Compare commits

..

50 Commits

Author SHA1 Message Date
46b98316ee fix: add minify mode 2023-05-06 10:42:08 +08:00
7320eaf54d chore: dealing with conflict 2023-05-05 15:52:01 +08:00
ae025850b0 feat: add bunder 2023-05-03 20:09:26 +08:00
febc297d5e feat: add build 2023-05-03 19:43:56 +08:00
5338a2fe92 Merge branch 'main' of github.com:antd-tiny-vue/antd-tiny-vue into main 2023-04-27 11:30:47 +08:00
b486c0ae40 chore: migrate vue3.3 2023-04-27 11:30:19 +08:00
01eb9af46c chore: change 2023-04-20 20:48:57 +08:00
7f296bc378 Merge branch 'main' of github.com:antd-tiny-vue/antd-tiny-vue into main 2023-04-19 10:46:46 +08:00
6096c28669 feat: add loading icon 2023-04-19 08:30:24 +08:00
786877b629 chore: change 2023-04-19 08:16:23 +08:00
4e0a756239 chore: resolve conflict 2023-04-18 15:50:37 +08:00
79210d99c9 chore: change 2023-04-18 15:49:28 +08:00
7bc8ec1094 chore: change docs build 2023-04-17 21:07:30 +08:00
08f17ce876 chore: change postion 2023-04-17 21:03:25 +08:00
fe029c5a0e chore: change docs 2023-04-17 21:03:02 +08:00
0d1e4d3e7c fix: space error 2023-04-17 11:30:22 +08:00
07d0ff0785 chore: change docs 2023-04-17 10:56:09 +08:00
2208af0174 fix: add space children 2023-04-17 10:55:29 +08:00
63ac0cb613 chore: add info 2023-04-17 10:51:11 +08:00
7cead859d1 chore: change info 2023-04-17 10:09:13 +08:00
7ae7f3a878 chore: change packageManger 2023-04-17 09:36:14 +08:00
27f2980768 feat: add space 2023-04-17 08:08:22 +08:00
e5896c093a feat: add info 2023-04-16 21:52:44 +08:00
5d5653d3bd chore: change rewrites 2023-04-16 18:34:47 +08:00
682dc06cdc chore: delete forget 2023-04-16 18:13:18 +08:00
9f19ed8c74 feat: add locale 2023-04-16 18:09:13 +08:00
bed231bfb2 chore: add docs 2023-04-08 10:08:35 +08:00
f394f48853 chore: change 2023-03-28 08:25:18 +08:00
163e29dfd1 chore(docs): change 2023-03-27 17:50:54 +08:00
3ba0d997d5 feat: add button 2023-03-27 07:51:19 +08:00
2b2ef3a3fb feat: add config 2023-03-26 15:49:59 +08:00
59e8b4d56e feat: add config provider 2023-03-25 17:49:55 +08:00
f64639a50c feat: add button 2023-03-25 17:16:46 +08:00
9243370f1c feat: add wave effect 2023-03-18 21:16:41 +08:00
69388270af feat: add wave 2023-03-18 20:48:10 +08:00
691bd2b965 feat: add wave 2023-03-18 18:17:20 +08:00
21c3a13f5a fix: use vc-utils createInjectionState replace vueuse 2023-03-17 22:47:03 +08:00
77bef2a548 feat: add button 2023-03-17 22:38:25 +08:00
6f22f963b4 feat: add global register 2023-03-10 22:18:22 +08:00
1e19c7fac2 feat: add button 2023-03-10 22:11:33 +08:00
a2dd8a0336 fix: fix hashId is not reactivty 2023-03-10 21:39:50 +08:00
74bb3d43c2 feat: add ant-design theme 2023-03-06 07:30:21 +08:00
8222f10259 feat: add delete button 2023-03-06 07:29:33 +08:00
770471fc3d feat: add basic 2023-03-06 07:28:23 +08:00
9603871a40 feat: add theme 2023-03-05 22:23:19 +08:00
8ab55b28fe feat: add theme and style 2023-03-02 07:16:36 +08:00
7b58fb84eb feat: add site group 2023-02-26 09:13:45 +08:00
f732c214e3 feat: add site demo 2023-02-26 08:20:18 +08:00
74e359a47d chore: add gitignore 2023-02-20 22:12:04 +08:00
b292e6ac83 feat: add site 2023-02-20 22:11:22 +08:00
105 changed files with 10242 additions and 965 deletions

5
.eslintignore Normal file
View File

@ -0,0 +1,5 @@
cache
.temp
*.log
es
lib

4
.gitignore vendored
View File

@ -3,3 +3,7 @@ node_modules
.vscode
.DS_Store
dist
cache
.temp
es
lib

View File

@ -0,0 +1,17 @@
<script lang="ts" setup>
import { onMounted, shallowRef } from 'vue'
const comp = shallowRef()
onMounted(async () => {
const { AntdTheme } = await import('vite-plugin-vitepress-demo/theme')
comp.value = AntdTheme
})
</script>
<template>
<ClientOnly>
<component v-bind="$attrs" :is="comp"></component>
</ClientOnly>
</template>
<style scoped></style>

42
.vitepress/config.ts Normal file
View File

@ -0,0 +1,42 @@
import { fileURLToPath } from 'url'
import { resolve } from 'path'
import { defineConfig } from 'vitepress'
import vueJsxPlugin from '@vitejs/plugin-vue-jsx'
import VitePluginVitepressDemo from 'vite-plugin-vitepress-demo'
import getEnUSConfig from './config/en-US'
import getZhCNConfig from './config/zh-CN'
import { getRewrites } from './config/rewrites'
const baseSrc = fileURLToPath(new URL('./', import.meta.url))
export default defineConfig({
rewrites: getRewrites(),
lang: 'en-US',
ignoreDeadLinks: true,
locales: {
'zh-CN': {
lang: 'zh-CN',
title: 'Antd Tiny Vue',
label: '简体中文',
description: 'vue3组件库站点',
themeConfig: getZhCNConfig()
},
root: {
lang: 'en-US',
title: 'Antd Tiny Vue',
label: 'English',
description: 'vue3 component library site',
themeConfig: getEnUSConfig()
}
},
vite: {
plugins: [vueJsxPlugin(), VitePluginVitepressDemo()],
resolve: {
alias: {
'antd-tiny-vue': resolve(baseSrc, '../components')
}
},
server: {
port: 1199
}
}
})

View File

@ -0,0 +1,9 @@
import type { DefaultTheme } from 'vitepress'
import { getNav } from './nav'
import { getSidebar } from './sidebar'
export default (): DefaultTheme.Config => ({
nav: getNav(),
sidebar: getSidebar(),
i18nRouting: true
})

View File

@ -0,0 +1,10 @@
import type { DefaultTheme } from 'vitepress'
export const getNav = (): DefaultTheme.NavItem[] => {
return [
{
text: 'Components',
link: '/components/'
}
]
}

View File

@ -0,0 +1,48 @@
import type { DefaultTheme } from 'vitepress'
const componentsDir = `/components/`
export const getSidebar = (): DefaultTheme.Sidebar => {
return {
[componentsDir]: [
{
text: 'General',
items: [
{
text: 'Button',
link: `${componentsDir}button/`
}
]
},
{
text: 'Layout',
items: []
},
{
text: 'Navigation',
items: []
},
{
text: 'Data Entry',
items: []
},
{
text: 'Data Display',
items: []
},
{
text: 'Feedback',
items: []
},
{
text: 'Other',
items: [
{
text: 'ConfigProvider',
link: `${componentsDir}config-provider/`
}
]
}
]
}
}

View File

@ -0,0 +1,9 @@
export const getRewrites = (): Record<string, string> => {
return {
'site/index.md': 'index.md',
'site/index.zh-CN.md': 'zh-CN/index.md',
'site/components/index.md': 'components/index.md',
'site/components/index.zh-CN.md': 'zh-CN/components/index.md',
'components/:comp/index.zh-CN.md': 'zh-CN/components/:comp/index.md'
}
}

View File

@ -0,0 +1,9 @@
import type { DefaultTheme } from 'vitepress'
import { getNav } from './nav'
import { getSidebar } from './sidebar'
export default (): DefaultTheme.Config => ({
nav: getNav(),
sidebar: getSidebar(),
i18nRouting: true
})

View File

@ -0,0 +1,10 @@
import type { DefaultTheme } from 'vitepress'
export const getNav = (): DefaultTheme.NavItem[] => {
return [
{
text: '组件',
link: '/zh-CN/components/'
}
]
}

View File

@ -0,0 +1,47 @@
import type { DefaultTheme } from 'vitepress'
const componentsDir = `/zh-CN/components/`
export const getSidebar = (): DefaultTheme.Sidebar => {
return {
[componentsDir]: [
{
text: '通用',
items: [
{
text: 'Button 按钮',
link: `${componentsDir}button/`
}
]
},
{
text: '布局',
items: []
},
{
text: '导航',
items: []
},
{
text: '数据入录',
items: []
},
{
text: '数据展示',
items: []
},
{
text: '反馈',
items: []
},
{
text: '其他',
items: [
{
text: 'ConfigProvider',
link: `${componentsDir}config-provider/`
}
]
}
]
}
}

18
.vitepress/theme/index.ts Normal file
View File

@ -0,0 +1,18 @@
import type { Theme } from 'vitepress'
// eslint-disable-next-line import/no-named-as-default
import DefaultTheme from 'vitepress/theme'
import AntdTheme from '../components/demo.vue'
// import { AntdTheme } from 'vite-plugin-vitepress-demo/theme'
export default {
...DefaultTheme,
async enhanceApp(ctx) {
DefaultTheme.enhanceApp?.(ctx)
ctx.app.component('Demo', AntdTheme)
// @ts-expect-error this is a local module
if (!import.meta.env.SSR) {
// @ts-expect-error this is a local module
const Antd = (await import('antd-tiny-vue')).default
ctx.app.use(Antd)
}
}
} as Theme

View File

@ -0,0 +1,8 @@
import type { ComputedRef } from 'vue'
import { computed } from 'vue'
import { useProviderConfigState } from '../../config-provider/context'
export const useDisabled = (props: Record<string, any>) => {
const { componentDisabled } = useProviderConfigState()
return computed(() => props.disabled || componentDisabled.value) as ComputedRef<boolean>
}

View File

@ -0,0 +1,12 @@
import { useState } from '@v-c/utils'
import { onMounted } from 'vue'
import { detectFlexGapSupported } from '../style-checker'
export default () => {
const [flexible, setFlexible] = useState(false)
onMounted(() => {
setFlexible(detectFlexGapSupported())
})
return flexible
}

View File

@ -0,0 +1,9 @@
import type { ComputedRef } from 'vue'
import { computed } from 'vue'
import type { SizeType } from '../../config-provider/context'
import { useProviderConfigState } from '../../config-provider/context'
export const useSize = (props: Record<string, any>) => {
const { componentSize } = useProviderConfigState()
return computed(() => props.size || componentSize.value) as ComputedRef<SizeType>
}

View File

@ -0,0 +1,32 @@
import { canUseDom } from '@v-c/utils'
export const canUseDocElement = () =>
canUseDom() && window.document.documentElement
let flexGapSupported: boolean
export const detectFlexGapSupported = () => {
if (!canUseDocElement()) {
return false
}
if (flexGapSupported !== undefined) {
return flexGapSupported
}
// create flex container with row-gap set
const flex = document.createElement('div')
flex.style.display = 'flex'
flex.style.flexDirection = 'column'
flex.style.rowGap = '1px'
// create two, elements inside it
flex.appendChild(document.createElement('div'))
flex.appendChild(document.createElement('div'))
// append to the DOM (needed to obtain scrollHeight)
document.body.appendChild(flex)
flexGapSupported = flex.scrollHeight === 1 // flex container should be 1px high from the row-gap
document.body.removeChild(flex)
return flexGapSupported
}

View File

@ -0,0 +1,21 @@
import { warning as rcWarning, resetWarned } from '@v-c/utils'
export { resetWarned }
export function noop() {}
type Warning = (valid: boolean, component: string, message?: string) => void
// eslint-disable-next-line import/no-mutable-exports
let warning: Warning = noop
if (process.env.NODE_ENV !== 'production') {
warning = (valid, component, message) => {
rcWarning(valid, `[antd: ${component}] ${message}`)
// StrictMode will inject console which will not throw warning in React 17.
if (process.env.NODE_ENV === 'test') {
resetWarned()
}
}
}
export default warning

View File

@ -0,0 +1,60 @@
import type { VNode } from 'vue'
import { cloneVNode, computed, defineComponent, shallowRef } from 'vue'
import { booleanType, classNames, filterEmpty, isVisible } from '@v-c/utils'
import { useEventListener } from '@vueuse/core'
import { useProviderConfigState } from '../../config-provider/context'
import useStyle from './style'
import useWave from './use-wave'
const Wave = defineComponent({
name: 'Wave',
props: {
disabled: booleanType()
},
setup(props, { slots }) {
const { getPrefixCls } = useProviderConfigState()
const containerRef = shallowRef()
// ============================== Style ===============================
const prefixCls = computed(() => getPrefixCls('wave'))
const [, hashId] = useStyle(prefixCls)
const showWave = useWave(
containerRef,
computed(() => classNames(prefixCls.value, hashId.value))
)
const onClick = (e: MouseEvent) => {
const node = containerRef.value
const { disabled } = props
if (!node || node.nodeType !== 1 || disabled) {
return
}
// Fix radio button click twice
if (
(e.target as HTMLElement).tagName === 'INPUT' ||
!isVisible(e.target as HTMLElement) ||
// No need wave
!node.getAttribute ||
node.getAttribute('disabled') ||
(node as HTMLInputElement).disabled ||
node.className.includes('disabled') ||
node.className.includes('-leave')
) {
return
}
showWave()
}
useEventListener(containerRef, 'click', onClick, true)
return () => {
const children = slots.default?.()
const child = filterEmpty(children)[0]
if (!child) return null
return cloneVNode(child as VNode, { ref: containerRef })
}
}
})
export default Wave

View File

@ -0,0 +1,34 @@
import { genComponentStyleHook } from '../../theme/internal'
import type { FullToken, GenerateStyle } from '../../theme/internal'
export interface ComponentToken {}
export interface WaveToken extends FullToken<'Wave'> {}
const genWaveStyle: GenerateStyle<WaveToken> = token => {
const { componentCls, colorPrimary } = token
return {
[componentCls]: {
position: 'absolute',
background: 'transparent',
pointerEvents: 'none',
boxSizing: 'border-box',
color: `var(--wave-color, ${colorPrimary})`,
boxShadow: `0 0 0 0 currentcolor`,
opacity: 0.2,
// =================== Motion ===================
'&.wave-motion-appear': {
transition: [`box-shadow 0.4s ${token.motionEaseOutCirc}`, `opacity 2s ${token.motionEaseOutCirc}`].join(','),
'&-active': {
boxShadow: `0 0 0 6px currentcolor`,
opacity: 0
}
}
}
}
}
export default genComponentStyleHook('Wave', token => [genWaveStyle(token)])

View File

@ -0,0 +1,11 @@
import type { ComputedRef, Ref } from 'vue'
import showWaveEffect from './wave-effect'
export default function useWave(nodeRef: Ref<HTMLElement>, className: ComputedRef<string>): VoidFunction {
function showWave() {
// const node = nodeRef.va!
showWaveEffect(nodeRef, className)
}
return showWave
}

View File

@ -0,0 +1,35 @@
export function isNotGrey(color: string) {
// eslint-disable-next-line no-useless-escape
const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\d.]*)?\)/)
if (match && match[1] && match[2] && match[3]) {
return !(match[1] === match[2] && match[2] === match[3])
}
return true
}
export function isValidWaveColor(color: string) {
return (
color &&
color !== '#fff' &&
color !== '#ffffff' &&
color !== 'rgb(255, 255, 255)' &&
color !== 'rgba(255, 255, 255, 1)' &&
isNotGrey(color) &&
!/rgba\((?:\d*, ){3}0\)/.test(color) && // any transparent rgba color
color !== 'transparent'
)
}
export function getTargetWaveColor(node: HTMLElement) {
const { borderTopColor, borderColor, backgroundColor } = getComputedStyle(node)
if (isValidWaveColor(borderTopColor)) {
return borderTopColor
}
if (isValidWaveColor(borderColor)) {
return borderColor
}
if (isValidWaveColor(backgroundColor)) {
return backgroundColor
}
return null
}

View File

@ -0,0 +1,108 @@
import type { ComputedRef, Ref } from 'vue'
import { unrefElement, useResizeObserver } from '@vueuse/core'
import { computed, defineComponent, onMounted, render, shallowRef, toRef } from 'vue'
import { classNames, delayTimer, objectType, safeNextick, useState } from '@v-c/utils'
import { getTargetWaveColor } from './util'
function validateNum(value: number) {
return Number.isNaN(value) ? 0 : value
}
export const WaveEffect = defineComponent({
name: 'WaveEffect',
props: {
target: objectType<HTMLElement>()
},
setup(props, { attrs }) {
const divRef = shallowRef<HTMLDivElement | undefined>(undefined)
const [color, setWaveColor] = useState<string | null>(null)
const [borderRadius, setBorderRadius] = useState<number[]>([])
const [left, setLeft] = useState(0)
const [top, setTop] = useState(0)
const [width, setWidth] = useState(0)
const [height, setHeight] = useState(0)
const [enabled, setEnabled] = useState(false)
const [active, setActive] = useState(false)
const waveStyle = computed(() => {
const style: Record<string, any> = {
left: `${left.value}px`,
top: `${top.value}px`,
width: `${width.value}px`,
height: `${height.value}px`,
borderRadius: borderRadius.value.map(radius => `${radius}px`).join(' ')
}
if (color.value) {
style['--wave-color'] = color.value
}
return style
})
function syncPos() {
const { target } = props
const nodeStyle = getComputedStyle(target)
// Get wave color from target
setWaveColor(getTargetWaveColor(target))
const isStatic = nodeStyle.position === 'static'
// Rect
const { borderLeftWidth, borderTopWidth } = nodeStyle
setLeft(isStatic ? target.offsetLeft : validateNum(-parseFloat(borderLeftWidth)))
setTop(isStatic ? target.offsetTop : validateNum(-parseFloat(borderTopWidth)))
setWidth(target.offsetWidth)
setHeight(target.offsetHeight)
// Get border radius
const { borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius } = nodeStyle
setBorderRadius([borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius, borderBottomLeftRadius].map(radius => validateNum(parseFloat(radius))))
}
onMounted(async () => {
syncPos()
setEnabled(true)
await safeNextick()
setActive(true)
await delayTimer(5000)
const holder = divRef.value?.parentElement
holder!.parentElement?.removeChild(holder!)
})
const motionClassName = computed(() =>
classNames({
'wave-motion-appear': enabled.value,
'wave-motion': true
})
)
const motionClassNameActive = computed(() => ({
'wave-motion-appear-active': active.value
}))
useResizeObserver(toRef(props, 'target'), syncPos)
return () => {
if (!enabled.value) return null
return (
<div
ref={divRef}
class={[attrs.class, motionClassName.value, motionClassNameActive.value]}
style={waveStyle.value}
/>
)
}
}
})
export default function showWaveEffect(nodeRef: Ref<HTMLElement>, className: ComputedRef<string>) {
const node = unrefElement(nodeRef)
// Create holder
const holder = document.createElement('div')
holder.style.position = 'absolute'
holder.style.left = `0px`
holder.style.top = `0px`
node?.insertBefore(holder, node?.firstChild)
render(
<WaveEffect
target={node}
class={className.value}
/>,
holder
)
}

View File

@ -0,0 +1,90 @@
// import type { VNode, VNodeChild } from 'vue'
// import { isString } from '@v-c/utils'
import { Text } from 'vue'
import type { VNode } from 'vue'
const rxTwoCNChar = /^[\u4E00-\u9FA5]{2}$/
export const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar)
export function isUnBorderedButtonType(type?: ButtonType) {
return type === 'text' || type === 'link'
}
export const spaceChildren = (child: VNode, needInserted: boolean) => {
const SPACE = needInserted ? ' ' : ''
if (child.type === Text) {
let text = (child.children as string).trim()
if (isTwoCNChar(text)) {
text = text.split('').join(SPACE)
}
return <span>{text}</span>
}
return child
}
//
// function splitCNCharsBySpace(child: VNodeChild , needInserted: boolean) {
// if (child === null || child === undefined) {
// return;
// }
//
// const SPACE = needInserted ? ' ' : '';
//
// if (
// typeof child !== 'string' &&
// typeof child !== 'number' &&
// isString((child as VNode)) &&
// isTwoCNChar((child as VNode).children)
// ) {
// return cloneVNode(child as , {
// children: child.props.children.split('').join(SPACE),
// });
// }
//
// if (typeof child === 'string') {
// return isTwoCNChar(child) ? <span>{child.split('').join(SPACE)}</span> : <span>{child}</span>;
// }
//
// if (isFragment(child)) {
// return <span>{child}</span>;
// }
//
// return child;
// }
// export function spaceChildren(children: React.ReactNode, needInserted: boolean) {
// let isPrevChildPure: boolean = false;
// const childList: React.ReactNode[] = [];
//
// React.Children.forEach(children, (child) => {
// const type = typeof child;
// const isCurrentChildPure = type === 'string' || type === 'number';
// if (isPrevChildPure && isCurrentChildPure) {
// const lastIndex = childList.length - 1;
// const lastChild = childList[lastIndex];
// childList[lastIndex] = `${lastChild}${child}`;
// } else {
// childList.push(child);
// }
//
// isPrevChildPure = isCurrentChildPure;
// });
//
// return React.Children.map(childList, (child) =>
// splitCNCharsBySpace(child as React.ReactElement | string | number, needInserted),
// );
// }
const ButtonTypes = [
'default',
'primary',
'ghost',
'dashed',
'link',
'text'
] as const
export type ButtonType = (typeof ButtonTypes)[number]
const ButtonShapes = ['default', 'circle', 'round'] as const
export type ButtonShape = (typeof ButtonShapes)[number]
const ButtonHTMLTypes = ['submit', 'button', 'reset'] as const
export type ButtonHTMLType = (typeof ButtonHTMLTypes)[number]

View File

@ -0,0 +1,238 @@
import { computed, defineComponent, onMounted, shallowRef } from 'vue'
import { tryOnBeforeUnmount } from '@vueuse/core'
import {
classNames,
filterEmpty,
getSlotsProps,
runEvent,
useState
} from '@v-c/utils'
import { useProviderConfigState } from '../config-provider/context'
import warning from '../_util/warning'
import Wave from '../_util/wave'
import { useSize } from '../_util/hooks/size'
import { useDisabled } from '../_util/hooks/disabled'
import { useCompactItemContext } from '../space/compact'
import useStyle from './style'
import type { ButtonProps, LoadingConfigType } from './interface'
import { buttonProps } from './interface'
import {
isTwoCNChar,
isUnBorderedButtonType,
spaceChildren
} from './button-helper'
import LoadingIcon from './loading-icon'
type Loading = number | boolean
function getLoadingConfig(loading: ButtonProps['loading']): LoadingConfigType {
if (typeof loading === 'object' && loading) {
const delay = loading?.delay
const isDelay = !Number.isNaN(delay) && typeof delay === 'number'
return {
loading: false,
delay: isDelay ? delay : 0
}
}
return {
loading,
delay: 0
}
}
const Button = defineComponent({
name: 'AButton',
inheritAttrs: false,
__ANT_BUTTON: true,
props: {
...buttonProps
},
setup(props, { slots, attrs }) {
const { getPrefixCls, autoInsertSpaceInButton, direction } =
useProviderConfigState()
const prefixCls = computed(() => getPrefixCls('btn', props.prefixCls))
const [wrapSSR, hashId] = useStyle(prefixCls)
const size = useSize(props)
const { compactSize, compactItemClassnames } = useCompactItemContext(
prefixCls,
direction
)
const sizeCls = computed(() => {
const sizeClassNameMap: Record<string, any> = {
large: 'lg',
small: 'sm',
middle: undefined
}
const sizeFullName = compactSize?.value || size.value
return sizeClassNameMap[sizeFullName]
})
const disabled = useDisabled(props)
const buttonRef = shallowRef<HTMLButtonElement | null>(null)
const loadingOrDelay = computed(() => {
return getLoadingConfig(props.loading)
})
const [innerLoading, setLoading] = useState<Loading>(
loadingOrDelay.value.loading
)
const [hasTwoCNChar, setHasTwoCNChar] = useState(false)
let isNeedInserted = false
const fixTwoCNChar = () => {
// FIXME: for HOC usage like <FormatMessage />
if (!buttonRef.value || autoInsertSpaceInButton.value === false) {
return
}
const buttonText = buttonRef.value.textContent
if (isNeedInserted && isTwoCNChar(buttonText as string)) {
if (!hasTwoCNChar.value) {
setHasTwoCNChar(true)
}
} else if (hasTwoCNChar.value) {
setHasTwoCNChar(false)
}
}
let delayTimer: number | null = null
onMounted(() => {
if (loadingOrDelay.value.delay > 0) {
delayTimer = window.setTimeout(() => {
delayTimer = null
setLoading(true)
}, loadingOrDelay.value.delay)
} else {
setLoading(loadingOrDelay.value.loading)
}
})
function cleanupTimer() {
if (delayTimer) {
window.clearTimeout(delayTimer)
delayTimer = null
}
}
tryOnBeforeUnmount(() => {
cleanupTimer()
})
const handleClick = (e: MouseEvent) => {
// FIXME: https://github.com/ant-design/ant-design/issues/30207
if (innerLoading.value || disabled.value) {
e.preventDefault()
return
}
runEvent(props, 'onClick', e)
}
const showError = () => {
const { ghost, type } = props
const icon = getSlotsProps(slots, props, 'icon')
warning(
!(typeof icon === 'string' && icon.length > 2),
'Button',
`\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`
)
warning(
!(ghost && isUnBorderedButtonType(type)),
'Button',
"`link` or `text` button can't be a `ghost` button."
)
}
return () => {
const { shape, rootClassName, ghost, type, block, danger } = props
const icon = getSlotsProps(slots, props, 'icon')
const children = filterEmpty(slots.default?.() as any)
isNeedInserted =
children.length === 1 &&
!slots.icon &&
!isUnBorderedButtonType(props.type)
fixTwoCNChar()
showError()
const iconType = innerLoading.value ? 'loading' : icon
const autoInsertSpace = autoInsertSpaceInButton.value !== false
const hrefAndDisabled = attrs.href !== undefined && disabled.value
const classes = classNames(
prefixCls.value,
hashId.value,
{
[`${prefixCls.value}-${shape}`]: shape !== 'default' && shape,
[`${prefixCls.value}-${type}`]: type,
[`${prefixCls.value}-${sizeCls.value}`]: sizeCls.value,
[`${prefixCls.value}-icon-only`]:
!children && children !== 0 && !!iconType,
[`${prefixCls.value}-background-ghost`]:
ghost && !isUnBorderedButtonType(type),
[`${prefixCls.value}-loading`]: innerLoading.value,
[`${prefixCls.value}-two-chinese-chars`]:
hasTwoCNChar.value && autoInsertSpace && !innerLoading.value,
[`${prefixCls.value}-block`]: block,
[`${prefixCls.value}-dangerous`]: !!danger,
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
[`${prefixCls.value}-disabled`]: hrefAndDisabled
},
attrs.class,
compactItemClassnames.value,
rootClassName
)
const iconNode =
icon &&
(!innerLoading.value ? (
icon?.()
) : (
<LoadingIcon
existIcon={!!icon}
prefixCls={prefixCls.value}
loading={!!innerLoading.value}
/>
))
const kids =
children || children === 0
? spaceChildren(children[0] as any, isNeedInserted && autoInsertSpace)
: undefined
if (attrs.href !== undefined) {
return wrapSSR(
<a
{...attrs}
{...props}
class={classes}
onClick={handleClick}
ref={buttonRef}
>
{iconNode}
{kids}
</a>
)
}
let buttonNode = (
<button
{...attrs}
onClick={handleClick}
class={classes}
ref={buttonRef}
>
{iconNode}
{kids}
</button>
)
if (!isUnBorderedButtonType(type)) {
buttonNode = <Wave>{buttonNode}</Wave>
}
return wrapSSR(buttonNode)
}
}
})
export default Button

View File

@ -0,0 +1,25 @@
<docs lang="zh-CN">
---
title: 按钮类型
---
按钮有五种类型主按钮次按钮虚线按钮文本按钮和链接按钮主按钮在同一个操作区域最多出现一次
</docs>
<docs lang="en-US">
---
title: Type
---
There are `primary` button, `default` button, `dashed` button, `text` button and `link` button in antd.
</docs>
<template>
<a-space wrap>
<a-button type="primary">Primary Button</a-button>
<a-button>Default Button</a-button>
<a-button type="dashed">Dashed Button</a-button>
<a-button type="text">Text Button</a-button>
<a-button type="link">Link Button</a-button>
</a-space>
</template>

View File

@ -0,0 +1,66 @@
# Button
To trigger an operation.
## When To Use
A button means an operation (or a series of operations). Clicking a button will trigger corresponding business logic.
In Ant Design we provide 5 types of button.
- Primary button: indicate the main action, one primary button at most in one section.
- Default button: indicate a series of actions without priority.
- Dashed button: used for adding action commonly.
- Text button: used for the most secondary action.
- Link button: used for external links.
And 4 other properties additionally.
- `danger`: used for actions of risk, like deletion or authorization.
- `ghost`: used in situations with complex background, home pages usually.
- `disabled`: when actions are not available.
- `loading`: add loading spinner in button, avoiding multiple submits too.
## Examples
<demo src="./demo/basic.vue"></demo>
## API
Different button styles can be generated by setting Button properties. The recommended order is: `type` -> `shape` -> `size` -> `loading` -> `disabled`.
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| block | Option to fit button width to its parent width | boolean | false | |
| danger | Set the danger status of button | boolean | false | |
| disabled | Disabled state of button | boolean | false | |
| ghost | Make background transparent and invert text and border colors | boolean | false | |
| href | Redirect url of link button | string | - | |
| htmlType | Set the original html `type` of `button`, see: [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type) | string | `button` | |
| icon | Set the icon component of button | ReactNode | - | |
| loading | Set the loading status of button | boolean \| { delay: number } | false | |
| shape | Can be set button shape | `default` \| `circle` \| `round` | `default` | |
| size | Set the size of button | `large` \| `middle` \| `small` | `middle` | |
| target | Same as target attribute of a, works when href is specified | string | - | |
| type | Can be set to `primary` `ghost` `dashed` `link` `text` `default` | string | `default` | |
| onClick | Set the handler to handle `click` event | (event: MouseEvent) => void | - | |
It accepts all props which native buttons support.
## Design Token
## FAQ
### How to remove space between 2 chinese characters?
Following the Ant Design specification, we will add one space between if Button (exclude Text button and Link button) contains two Chinese characters only. If you don't need that, you can use [ConfigProvider](/components/config-provider/#api) to set `autoInsertSpaceInButton` as `false`.
<img src="https://gw.alipayobjects.com/zos/antfincdn/MY%26THAPZrW/38f06cb9-293a-4b42-b183-9f443e79ffea.png" style="box-shadow: none; margin: 0; width: 100px" alt="Button with two Chinese characters" />
<style>
.site-button-ghost-wrapper {
padding: 16px;
background: rgb(190, 200, 200);
}
</style>

View File

@ -0,0 +1,8 @@
import type { App } from 'vue'
import Button from './button'
Button.install = function (app: App) {
app.component(Button.name, Button)
}
export default Button

View File

@ -0,0 +1,75 @@
# Button 按钮
按钮用于开始一个即时操作。
## 何时使用
标记了一个(或封装一组)操作命令,响应用户点击行为,触发相应的业务逻辑。
在 Ant Design 中我们提供了五种按钮。
- 主按钮:用于主行动点,一个操作区域只能有一个主按钮。
- 默认按钮:用于没有主次之分的一组行动点。
- 虚线按钮:常用于添加操作。
- 文本按钮:用于最次级的行动点。
- 链接按钮:一般用于链接,即导航至某位置。
以及四种状态属性与上面配合使用。
- 危险:删除/移动/修改权限等危险操作,一般需要二次确认。
- 幽灵:用于背景色比较复杂的地方,常用在首页/产品页等展示场景。
- 禁用:行动点不可用的时候,一般需要文案解释。
- 加载中:用于异步操作等待反馈的时候,也可以避免多次提交。
[完整设计指南](https://ant.design/docs/spec/buttons-cn)
## 代码演示
<demo src="./demo/basic.vue"></demo>
## API
通过设置 Button 的属性来产生不同的按钮样式,推荐顺序为:`type` -> `shape` -> `size` -> `loading` -> `disabled`
按钮的属性说明如下:
| 属性 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| block | 将按钮宽度调整为其父宽度的选项 | boolean | false | |
| danger | 设置危险按钮 | boolean | false | |
| disabled | 设置按钮失效状态 | boolean | false | |
| ghost | 幽灵属性,使按钮背景透明 | boolean | false | |
| href | 点击跳转的地址,指定此属性 button 的行为和 a 链接一致 | string | - | |
| htmlType | 设置 `button` 原生的 `type` 值,可选值请参考 [HTML 标准](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type) | string | `button` | |
| icon | 设置按钮的图标组件 | ReactNode | - | |
| loading | 设置按钮载入状态 | boolean \| { delay: number } | false | |
| shape | 设置按钮形状 | `default` \| `circle` \| `round` | `default` | |
| size | 设置按钮大小 | `large` \| `middle` \| `small` | `middle` | |
| target | 相当于 a 链接的 target 属性href 存在时生效 | string | - | |
| type | 设置按钮类型 | `primary` \| `ghost` \| `dashed` \| `link` \| `text` \| `default` | `default` | |
| onClick | 点击按钮时的回调 | (event: MouseEvent) => void | - | |
支持原生 button 的其他所有属性。
## Design Token
## FAQ
### 如何移除两个汉字之间的空格?
根据 Ant Design 设计规范要求,我们会在按钮内(文本按钮和链接按钮除外)只有两个汉字时自动添加空格,如果你不需要这个特性,可以设置 [ConfigProvider](/zh-CN/components/config-provider/#api) 的 `autoInsertSpaceInButton``false`
<img src="https://gw.alipayobjects.com/zos/antfincdn/MY%26THAPZrW/38f06cb9-293a-4b42-b183-9f443e79ffea.png" style="box-shadow: none; margin: 0; width: 100px" alt="移除两个汉字之间的空格" />
<style>
.site-button-ghost-wrapper {
padding: 16px;
background: rgb(190, 200, 200);
}
</style>
## 设计指引
- [我的按钮究竟该放哪儿!?| Ant Design 4.0 系列分享](https://zhuanlan.zhihu.com/p/109644406)

View File

@ -0,0 +1,25 @@
import { booleanType, someType, stringType, vNodeType } from '@v-c/utils'
import type { ExtractPropTypes } from 'vue'
import type { SizeType } from '../config-provider/context'
import type { ButtonHTMLType, ButtonShape, ButtonType } from './button-helper'
export interface LoadingConfigType {
loading: boolean
delay: number
}
export const buttonProps = {
type: stringType<ButtonType>('default'),
icon: vNodeType(),
shape: stringType<ButtonShape>(),
size: someType<SizeType | 'default'>([String], 'default'),
disabled: booleanType(),
loading: someType<boolean | LoadingConfigType>(),
prefixCls: stringType(),
rootClassName: stringType(),
ghost: booleanType(),
danger: booleanType(),
block: booleanType(),
htmlType: stringType<ButtonHTMLType>('button')
}
export type ButtonProps = ExtractPropTypes<typeof buttonProps>

View File

@ -0,0 +1,67 @@
import { Transition, defineComponent, nextTick } from 'vue'
import { booleanType, someType, stringType } from '@v-c/utils'
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'
export interface LoadingIconProps {
prefixCls: string
existIcon: boolean
loading?: boolean | object
}
export const loadingIconProps = {
prefixCls: stringType(),
existIcon: booleanType(),
loading: someType<boolean | object>([Boolean, Object])
}
const getCollapsedWidth = (el: Element) => {
const node: HTMLElement = el as HTMLElement
if (node) {
node.style.width = '0'
node.style.opacity = '0'
node.style.transform = 'scale(0)'
}
}
const getRealWidth = (el: Element) => {
const node: HTMLElement = el as HTMLElement
nextTick(() => {
if (node) {
node.style.width = `${node.scrollWidth}px`
node.style.opacity = '1'
node.style.transform = 'scale(1)'
}
}).then()
}
const LoadingIcon = defineComponent({
name: 'LoadingIcon',
props: loadingIconProps,
setup(props) {
return () => {
const { loading, existIcon, prefixCls } = props
const visible = !!loading
if (existIcon) {
return (
<span class={`${prefixCls}-loading-icon`}>
<LoadingOutlined />
</span>
)
}
return (
<Transition
name={`${prefixCls}-loading-icon-motion`}
onBeforeEnter={getCollapsedWidth}
onEnter={getRealWidth}
>
{visible ? (
<span class={`${prefixCls}-loading-icon`}>
<LoadingOutlined />
</span>
) : null}
</Transition>
)
}
}
})
export default LoadingIcon

View File

@ -0,0 +1,80 @@
import type { GenerateStyle } from '../../theme/internal'
import type { ButtonToken } from '.'
const genButtonBorderStyle = (buttonTypeCls: string, borderColor: string) => ({
// Border
[`> span, > ${buttonTypeCls}`]: {
'&:not(:last-child)': {
[`&, & > ${buttonTypeCls}`]: {
'&:not(:disabled)': {
borderInlineEndColor: borderColor
}
}
},
'&:not(:first-child)': {
[`&, & > ${buttonTypeCls}`]: {
'&:not(:disabled)': {
borderInlineStartColor: borderColor
}
}
}
}
})
const genGroupStyle: GenerateStyle<ButtonToken> = token => {
const { componentCls, fontSize, lineWidth, colorPrimaryHover, colorErrorHover } = token
return {
[`${componentCls}-group`]: [
{
position: 'relative',
display: 'inline-flex',
// Border
[`> span, > ${componentCls}`]: {
'&:not(:last-child)': {
[`&, & > ${componentCls}`]: {
borderStartEndRadius: 0,
borderEndEndRadius: 0
}
},
'&:not(:first-child)': {
marginInlineStart: -lineWidth,
[`&, & > ${componentCls}`]: {
borderStartStartRadius: 0,
borderEndStartRadius: 0
}
}
},
[componentCls]: {
position: 'relative',
zIndex: 1,
[`&:hover,
&:focus,
&:active`]: {
zIndex: 2
},
'&[disabled]': {
zIndex: 0
}
},
[`${componentCls}-icon-only`]: {
fontSize
}
},
// Border Color
genButtonBorderStyle(`${componentCls}-primary`, colorPrimaryHover),
genButtonBorderStyle(`${componentCls}-danger`, colorErrorHover)
]
}
}
export default genGroupStyle

View File

@ -0,0 +1,553 @@
import type { CSSInterpolation, CSSObject } from '@antd-tiny-vue/cssinjs'
import type { FullToken, GenerateStyle } from '../../theme/internal'
import { genComponentStyleHook, mergeToken } from '../../theme/internal'
import { genFocusStyle } from '../../style'
import { genCompactItemStyle } from '../../style/compact-item'
import { genCompactItemVerticalStyle } from '../../style/compact-item-vertical'
import genGroupStyle from './group'
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {}
export interface ButtonToken extends FullToken<'Button'> {
// FIXME: should be removed
colorOutlineDefault: string
buttonPaddingHorizontal: number
}
// ============================== Shared ==============================
const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (
token
): CSSObject => {
const { componentCls, iconCls } = token
return {
[componentCls]: {
outline: 'none',
position: 'relative',
display: 'inline-block',
fontWeight: 400,
whiteSpace: 'nowrap',
textAlign: 'center',
backgroundImage: 'none',
backgroundColor: 'transparent',
border: `${token.lineWidth}px ${token.lineType} transparent`,
cursor: 'pointer',
transition: `all ${token.motionDurationMid} ${token.motionEaseInOut}`,
userSelect: 'none',
touchAction: 'manipulation',
lineHeight: token.lineHeight,
color: token.colorText,
'> span': {
display: 'inline-block'
},
// Leave a space between icon and text.
[`> ${iconCls} + span, > span + ${iconCls}`]: {
marginInlineStart: token.marginXS
},
'> a': {
color: 'currentColor'
},
'&:not(:disabled)': {
...genFocusStyle(token)
},
// make `btn-icon-only` not too narrow
[`&-icon-only${componentCls}-compact-item`]: {
flex: 'none'
},
// Special styles for Primary Button
[`&-compact-item${componentCls}-primary`]: {
[`&:not([disabled]) + ${componentCls}-compact-item${componentCls}-primary:not([disabled])`]:
{
position: 'relative',
'&:before': {
position: 'absolute',
top: -token.lineWidth,
insetInlineStart: -token.lineWidth,
display: 'inline-block',
width: token.lineWidth,
height: `calc(100% + ${token.lineWidth * 2}px)`,
backgroundColor: token.colorPrimaryHover,
content: '""'
}
}
},
// Special styles for Primary Button
'&-compact-vertical-item': {
[`&${componentCls}-primary`]: {
[`&:not([disabled]) + ${componentCls}-compact-vertical-item${componentCls}-primary:not([disabled])`]:
{
position: 'relative',
'&:before': {
position: 'absolute',
top: -token.lineWidth,
insetInlineStart: -token.lineWidth,
display: 'inline-block',
width: `calc(100% + ${token.lineWidth * 2}px)`,
height: token.lineWidth,
backgroundColor: token.colorPrimaryHover,
content: '""'
}
}
}
}
}
}
}
const genHoverActiveButtonStyle = (
hoverStyle: CSSObject,
activeStyle: CSSObject
): CSSObject => ({
'&:not(:disabled)': {
'&:hover': hoverStyle,
'&:active': activeStyle
}
})
// ============================== Shape ===============================
const genCircleButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (
token
) => ({
minWidth: token.controlHeight,
paddingInlineStart: 0,
paddingInlineEnd: 0,
borderRadius: '50%'
})
const genRoundButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token) => ({
borderRadius: token.controlHeight,
paddingInlineStart: token.controlHeight / 2,
paddingInlineEnd: token.controlHeight / 2
})
// =============================== Type ===============================
const genDisabledStyle: GenerateStyle<ButtonToken, CSSObject> = (token) => ({
cursor: 'not-allowed',
borderColor: token.colorBorder,
color: token.colorTextDisabled,
backgroundColor: token.colorBgContainerDisabled,
boxShadow: 'none'
})
const genGhostButtonStyle = (
btnCls: string,
textColor: string | false,
borderColor: string | false,
textColorDisabled: string | false,
borderColorDisabled: string | false,
hoverStyle?: CSSObject,
activeStyle?: CSSObject
): CSSObject => ({
[`&${btnCls}-background-ghost`]: {
color: textColor || undefined,
backgroundColor: 'transparent',
borderColor: borderColor || undefined,
boxShadow: 'none',
...genHoverActiveButtonStyle(
{
backgroundColor: 'transparent',
...hoverStyle
},
{
backgroundColor: 'transparent',
...activeStyle
}
),
'&:disabled': {
cursor: 'not-allowed',
color: textColorDisabled || undefined,
borderColor: borderColorDisabled || undefined
}
}
})
const genSolidDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (
token
) => ({
'&:disabled': {
...genDisabledStyle(token)
}
})
const genSolidButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token) => ({
...genSolidDisabledButtonStyle(token)
})
const genPureDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (
token
) => ({
'&:disabled': {
cursor: 'not-allowed',
color: token.colorTextDisabled
}
})
// Type: Default
const genDefaultButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (
token
) => ({
...genSolidButtonStyle(token),
backgroundColor: token.colorBgContainer,
borderColor: token.colorBorder,
boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlTmpOutline}`,
...genHoverActiveButtonStyle(
{
color: token.colorPrimaryHover,
borderColor: token.colorPrimaryHover
},
{
color: token.colorPrimaryActive,
borderColor: token.colorPrimaryActive
}
),
...genGhostButtonStyle(
token.componentCls,
token.colorBgContainer,
token.colorBgContainer,
token.colorTextDisabled,
token.colorBorder
),
[`&${token.componentCls}-dangerous`]: {
color: token.colorError,
borderColor: token.colorError,
...genHoverActiveButtonStyle(
{
color: token.colorErrorHover,
borderColor: token.colorErrorBorderHover
},
{
color: token.colorErrorActive,
borderColor: token.colorErrorActive
}
),
...genGhostButtonStyle(
token.componentCls,
token.colorError,
token.colorError,
token.colorTextDisabled,
token.colorBorder
),
...genSolidDisabledButtonStyle(token)
}
})
// Type: Primary
const genPrimaryButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (
token
) => ({
...genSolidButtonStyle(token),
color: token.colorTextLightSolid,
backgroundColor: token.colorPrimary,
boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlOutline}`,
...genHoverActiveButtonStyle(
{
color: token.colorTextLightSolid,
backgroundColor: token.colorPrimaryHover
},
{
color: token.colorTextLightSolid,
backgroundColor: token.colorPrimaryActive
}
),
...genGhostButtonStyle(
token.componentCls,
token.colorPrimary,
token.colorPrimary,
token.colorTextDisabled,
token.colorBorder,
{
color: token.colorPrimaryHover,
borderColor: token.colorPrimaryHover
},
{
color: token.colorPrimaryActive,
borderColor: token.colorPrimaryActive
}
),
[`&${token.componentCls}-dangerous`]: {
backgroundColor: token.colorError,
boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.colorErrorOutline}`,
...genHoverActiveButtonStyle(
{
backgroundColor: token.colorErrorHover
},
{
backgroundColor: token.colorErrorActive
}
),
...genGhostButtonStyle(
token.componentCls,
token.colorError,
token.colorError,
token.colorTextDisabled,
token.colorBorder,
{
color: token.colorErrorHover,
borderColor: token.colorErrorHover
},
{
color: token.colorErrorActive,
borderColor: token.colorErrorActive
}
),
...genSolidDisabledButtonStyle(token)
}
})
// Type: Dashed
const genDashedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (
token
) => ({
...genDefaultButtonStyle(token),
borderStyle: 'dashed'
})
// Type: Link
const genLinkButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token) => ({
color: token.colorLink,
...genHoverActiveButtonStyle(
{
color: token.colorLinkHover
},
{
color: token.colorLinkActive
}
),
...genPureDisabledButtonStyle(token),
[`&${token.componentCls}-dangerous`]: {
color: token.colorError,
...genHoverActiveButtonStyle(
{
color: token.colorErrorHover
},
{
color: token.colorErrorActive
}
),
...genPureDisabledButtonStyle(token)
}
})
// Type: Text
const genTextButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token) => ({
...genHoverActiveButtonStyle(
{
color: token.colorText,
backgroundColor: token.colorBgTextHover
},
{
color: token.colorText,
backgroundColor: token.colorBgTextActive
}
),
...genPureDisabledButtonStyle(token),
[`&${token.componentCls}-dangerous`]: {
color: token.colorError,
...genPureDisabledButtonStyle(token),
...genHoverActiveButtonStyle(
{
color: token.colorErrorHover,
backgroundColor: token.colorErrorBg
},
{
color: token.colorErrorHover,
backgroundColor: token.colorErrorBg
}
)
}
})
// Href and Disabled
const genDisabledButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (
token
) => ({
...genDisabledStyle(token),
[`&${token.componentCls}:hover`]: {
...genDisabledStyle(token)
}
})
const genTypeButtonStyle: GenerateStyle<ButtonToken> = (token) => {
const { componentCls } = token
return {
[`${componentCls}-default`]: genDefaultButtonStyle(token),
[`${componentCls}-primary`]: genPrimaryButtonStyle(token),
[`${componentCls}-dashed`]: genDashedButtonStyle(token),
[`${componentCls}-link`]: genLinkButtonStyle(token),
[`${componentCls}-text`]: genTextButtonStyle(token),
[`${componentCls}-disabled`]: genDisabledButtonStyle(token)
}
}
// =============================== Size ===============================
const genSizeButtonStyle = (
token: ButtonToken,
sizePrefixCls = ''
): CSSInterpolation => {
const {
componentCls,
iconCls,
controlHeight,
fontSize,
lineHeight,
lineWidth,
borderRadius,
buttonPaddingHorizontal
} = token
const paddingVertical = Math.max(
0,
(controlHeight - fontSize * lineHeight) / 2 - lineWidth
)
const paddingHorizontal = buttonPaddingHorizontal - lineWidth
const iconOnlyCls = `${componentCls}-icon-only`
return [
// Size
{
[`${componentCls}${sizePrefixCls}`]: {
fontSize,
height: controlHeight,
padding: `${paddingVertical}px ${paddingHorizontal}px`,
borderRadius,
[`&${iconOnlyCls}`]: {
width: controlHeight,
paddingInlineStart: 0,
paddingInlineEnd: 0,
[`&${componentCls}-round`]: {
width: 'auto'
},
'> span': {
transform: 'scale(1.143)' // 14px -> 16px
}
},
// Loading
[`&${componentCls}-loading`]: {
opacity: token.opacityLoading,
cursor: 'default'
},
[`${componentCls}-loading-icon`]: {
transition: `width ${token.motionDurationSlow} ${token.motionEaseInOut}, opacity ${token.motionDurationSlow} ${token.motionEaseInOut}`
},
[`&:not(${iconOnlyCls}) ${componentCls}-loading-icon > ${iconCls}`]: {
marginInlineEnd: token.marginXS
}
}
},
// Shape - patch prefixCls again to override solid border radius style
{
[`${componentCls}${componentCls}-circle${sizePrefixCls}`]:
genCircleButtonStyle(token)
},
{
[`${componentCls}${componentCls}-round${sizePrefixCls}`]:
genRoundButtonStyle(token)
}
]
}
const genSizeBaseButtonStyle: GenerateStyle<ButtonToken> = (token) =>
genSizeButtonStyle(token)
const genSizeSmallButtonStyle: GenerateStyle<ButtonToken> = (token) => {
const smallToken = mergeToken<ButtonToken>(token, {
controlHeight: token.controlHeightSM,
padding: token.paddingXS,
buttonPaddingHorizontal: 8, // Fixed padding
borderRadius: token.borderRadiusSM
})
return genSizeButtonStyle(smallToken, `${token.componentCls}-sm`)
}
const genSizeLargeButtonStyle: GenerateStyle<ButtonToken> = (token) => {
const largeToken = mergeToken<ButtonToken>(token, {
controlHeight: token.controlHeightLG,
fontSize: token.fontSizeLG,
borderRadius: token.borderRadiusLG
})
return genSizeButtonStyle(largeToken, `${token.componentCls}-lg`)
}
const genBlockButtonStyle: GenerateStyle<ButtonToken> = (token) => {
const { componentCls } = token
return {
[componentCls]: {
[`&${componentCls}-block`]: {
width: '100%'
}
}
}
}
// ============================== Export ==============================
export default genComponentStyleHook('Button', (token) => {
const { controlTmpOutline, paddingContentHorizontal } = token
const buttonToken = mergeToken<ButtonToken>(token, {
colorOutlineDefault: controlTmpOutline,
buttonPaddingHorizontal: paddingContentHorizontal
})
return [
// Shared
genSharedButtonStyle(buttonToken),
// Size
genSizeSmallButtonStyle(buttonToken),
genSizeBaseButtonStyle(buttonToken),
genSizeLargeButtonStyle(buttonToken),
// Block
genBlockButtonStyle(buttonToken),
// Group (type, ghost, danger, disabled, loading)
genTypeButtonStyle(buttonToken),
// Button Group
genGroupStyle(buttonToken),
// Space Compact
genCompactItemStyle(token, { focus: false }),
genCompactItemVerticalStyle(token)
]
})

3
components/components.ts Normal file
View File

@ -0,0 +1,3 @@
export { default as Button } from './button'
export { default as ConfigProvider } from './config-provider'
export { default as Space } from './space'

View File

@ -0,0 +1,142 @@
import {
booleanType,
createInjectionState,
functionType,
objectType,
someType,
stringType
} from '@v-c/utils'
import type { ExtractPropTypes } from 'vue'
import { computed } from 'vue'
import type { DerivativeFunc } from '@antd-tiny-vue/cssinjs'
import type {
AliasToken,
MapToken,
OverrideToken,
SeedToken
} from '../theme/interface'
import type { RenderEmptyHandler } from './default-render-empty'
import type { ConfigProviderProps } from './index'
export type SizeType = 'small' | 'middle' | 'large' | undefined
export interface Theme {
primaryColor?: string
infoColor?: string
successColor?: string
processingColor?: string
errorColor?: string
warningColor?: string
}
export interface CSPConfig {
nonce?: string
}
export type DirectionType = 'ltr' | 'rtl' | undefined
export type MappingAlgorithm = DerivativeFunc<SeedToken, MapToken>
export interface ThemeConfig {
token?: Partial<AliasToken>
components?: OverrideToken
algorithm?: MappingAlgorithm | MappingAlgorithm[]
hashed?: boolean
inherit?: boolean
}
export const defaultIconPrefixCls = 'anticon'
const defaultGetPrefixCls = (
suffixCls?: string,
customizePrefixCls?: string
) => {
if (customizePrefixCls) return customizePrefixCls
return suffixCls ? `ant-${suffixCls}` : 'ant'
}
export const configConsumerProps = {
getTargetContainer: functionType<() => HTMLElement>(),
getPopupContainer: functionType<(triggerNode?: HTMLElement) => HTMLElement>(),
rootPrefixCls: stringType(),
iconPrefixCls: stringType(defaultIconPrefixCls),
getPrefixCls: functionType(defaultGetPrefixCls),
renderEmpty: functionType<RenderEmptyHandler>(),
csp: objectType<CSPConfig>(),
autoInsertSpaceInButton: booleanType(),
input: objectType<{
autoComplete?: string
}>(),
pagination: objectType<{
showSizeChanger?: boolean
}>(),
locale: objectType(),
pageHeader: objectType<{
ghost: boolean
}>(),
direction: someType<DirectionType>([String]),
space: objectType<{
size?: SizeType | number
}>(),
virtual: booleanType(),
dropdownMatchSelectWidth: booleanType(),
form: objectType<{
// requiredMark?: RequiredMark
colon?: boolean
// scrollToFirstError?: Options | boolean
}>(),
theme: objectType<ThemeConfig>(),
select: objectType<{
showSearch?: boolean
}>()
}
export type ConfigConsumerProps = ExtractPropTypes<typeof configConsumerProps>
const configState = (props: ConfigProviderProps) => {
const getPrefixCls = (suffixCls?: string, customizePrefixCls?: string) => {
const { prefixCls, getPrefixCls } = props
if (customizePrefixCls) return customizePrefixCls
const mergedPrefixCls =
prefixCls || getPrefixCls?.('') || defaultGetPrefixCls('')
return suffixCls ? `${mergedPrefixCls}-${suffixCls}` : mergedPrefixCls
}
const iconPrefixCls = computed(
() => props?.iconPrefixCls ?? defaultIconPrefixCls
)
const shouldWrapSSR = computed(
() => iconPrefixCls.value !== defaultIconPrefixCls
)
const csp = computed(() => props?.csp)
const componentSize = computed(() => props?.componentSize)
const componentDisabled = computed(() => props?.componentDisabled)
const autoInsertSpaceInButton = computed(() => props?.autoInsertSpaceInButton)
const direction = computed(() => props?.direction)
return {
getPrefixCls,
iconPrefixCls,
shouldWrapSSR,
csp,
componentSize,
componentDisabled,
autoInsertSpaceInButton,
direction
}
}
const [useProviderConfigProvide, useProviderConfigInject] =
createInjectionState(configState)
export { useProviderConfigProvide }
export const useProviderConfigState = (): ReturnType<typeof configState> => {
return (
useProviderConfigInject() ??
({
getPrefixCls: defaultGetPrefixCls,
iconPrefixCls: computed(() => defaultIconPrefixCls),
componentSize: computed(() => undefined),
componentDisabled: computed(() => false),
direction: computed(() => undefined),
autoInsertSpaceInButton: computed(() => true)
} as any)
)
}

View File

@ -0,0 +1,94 @@
/* eslint-disable import/prefer-default-export, prefer-destructuring */
import { generate } from '@ant-design/colors'
import { TinyColor } from '@ctrl/tinycolor'
import { canUseDom, updateCSS, warning } from '@v-c/utils'
import type { Theme } from './context'
const dynamicStyleMark = `-ant-${Date.now()}-${Math.random()}`
export function getStyle(globalPrefixCls: string, theme: Theme) {
const variables: Record<string, string> = {}
const formatColor = (color: TinyColor, updater?: (cloneColor: TinyColor) => TinyColor) => {
let clone = color.clone()
clone = updater?.(clone) || clone
return clone.toRgbString()
}
const fillColor = (colorVal: string, type: string) => {
const baseColor = new TinyColor(colorVal)
const colorPalettes = generate(baseColor.toRgbString())
variables[`${type}-color`] = formatColor(baseColor)
variables[`${type}-color-disabled`] = colorPalettes[1]
variables[`${type}-color-hover`] = colorPalettes[4]
variables[`${type}-color-active`] = colorPalettes[6]
variables[`${type}-color-outline`] = baseColor.clone().setAlpha(0.2).toRgbString()
variables[`${type}-color-deprecated-bg`] = colorPalettes[0]
variables[`${type}-color-deprecated-border`] = colorPalettes[2]
}
// ================ Primary Color ================
if (theme.primaryColor) {
fillColor(theme.primaryColor, 'primary')
const primaryColor = new TinyColor(theme.primaryColor)
const primaryColors = generate(primaryColor.toRgbString())
// Legacy - We should use semantic naming standard
primaryColors.forEach((color, index) => {
variables[`primary-${index + 1}`] = color
})
// Deprecated
variables['primary-color-deprecated-l-35'] = formatColor(primaryColor, c => c.lighten(35))
variables['primary-color-deprecated-l-20'] = formatColor(primaryColor, c => c.lighten(20))
variables['primary-color-deprecated-t-20'] = formatColor(primaryColor, c => c.tint(20))
variables['primary-color-deprecated-t-50'] = formatColor(primaryColor, c => c.tint(50))
variables['primary-color-deprecated-f-12'] = formatColor(primaryColor, c => c.setAlpha(c.getAlpha() * 0.12))
const primaryActiveColor = new TinyColor(primaryColors[0])
variables['primary-color-active-deprecated-f-30'] = formatColor(primaryActiveColor, c => c.setAlpha(c.getAlpha() * 0.3))
variables['primary-color-active-deprecated-d-02'] = formatColor(primaryActiveColor, c => c.darken(2))
}
// ================ Success Color ================
if (theme.successColor) {
fillColor(theme.successColor, 'success')
}
// ================ Warning Color ================
if (theme.warningColor) {
fillColor(theme.warningColor, 'warning')
}
// ================= Error Color =================
if (theme.errorColor) {
fillColor(theme.errorColor, 'error')
}
// ================= Info Color ==================
if (theme.infoColor) {
fillColor(theme.infoColor, 'info')
}
// Convert to css variables
const cssList = Object.keys(variables).map(key => `--${globalPrefixCls}-${key}: ${variables[key]};`)
return `
:root {
${cssList.join('\n')}
}
`.trim()
}
export function registerTheme(globalPrefixCls: string, theme: Theme) {
const style = getStyle(globalPrefixCls, theme)
if (canUseDom()) {
updateCSS(style, `${dynamicStyleMark}-dynamic-theme`)
} else {
// @ts-expect-error this is ssr
warning(false, 'ConfigProvider', 'SSR do not support dynamic theme with css variables.')
}
}

View File

@ -0,0 +1,3 @@
import type { VNodeChild } from 'vue'
export type RenderEmptyHandler = (componentName?: string) => VNodeChild

View File

@ -0,0 +1,9 @@
import { useProviderConfigState } from '../context'
export const useConfig = () => {
const { componentDisabled, componentSize } = useProviderConfigState()
return {
componentDisabled,
componentSize
}
}

View File

@ -0,0 +1 @@
# ConfigProvider

View File

@ -0,0 +1,162 @@
import { booleanType, objectType, someType, stringType } from '@v-c/utils'
import type { App, ExtractPropTypes } from 'vue'
import { computed, defineComponent } from 'vue'
import { createTheme } from '@antd-tiny-vue/cssinjs'
import defaultSeedToken from '../theme/themes/seed'
import type { DesignTokenConfig } from '../theme/internal'
import { DesignTokenProviderContext } from '../theme/internal'
import type { CSPConfig, ConfigConsumerProps, DirectionType, SizeType, Theme, ThemeConfig } from './context'
import { configConsumerProps, defaultIconPrefixCls, useProviderConfigProvide } from './context'
import { registerTheme } from './css-variables'
import type { RenderEmptyHandler } from './default-render-empty'
import useStyle from './style'
import { useConfig } from './hooks/config'
export type { RenderEmptyHandler, CSPConfig, DirectionType, ConfigConsumerProps, ThemeConfig }
export { defaultIconPrefixCls }
export const configProviderProps = {
...configConsumerProps,
prefixCls: stringType(),
componentSize: someType<SizeType>([String]),
componentDisabled: booleanType(),
legacyLocale: objectType()
}
export type ConfigProviderProps = Partial<ExtractPropTypes<typeof configProviderProps>>
export const defaultPrefixCls = 'ant'
// These props is used by `useContext` directly in sub component
// const PASSED_PROPS: Exclude<keyof ConfigConsumerProps, 'rootPrefixCls' | 'getPrefixCls'>[] = [
// 'getTargetContainer',
// 'getPopupContainer',
// 'renderEmpty',
// 'pageHeader',
// 'input',
// 'pagination',
// 'form',
// 'select'
// ]
let globalPrefixCls: string
let globalIconPrefixCls: string
function getGlobalPrefixCls() {
return globalPrefixCls || defaultPrefixCls
}
function getGlobalIconPrefixCls() {
return globalIconPrefixCls || defaultIconPrefixCls
}
export const setGlobalConfig = ({ prefixCls, iconPrefixCls, theme }: Pick<ConfigProviderProps, 'prefixCls' | 'iconPrefixCls'> & { theme?: Theme }) => {
if (prefixCls !== undefined) {
globalPrefixCls = prefixCls
}
if (iconPrefixCls !== undefined) {
globalIconPrefixCls = iconPrefixCls
}
if (theme) {
registerTheme(getGlobalPrefixCls(), theme)
}
}
export const globalConfig = () => ({
getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => {
if (customizePrefixCls) return customizePrefixCls
return suffixCls ? `${getGlobalPrefixCls()}-${suffixCls}` : getGlobalPrefixCls()
},
getIconPrefixCls: getGlobalIconPrefixCls,
getRootPrefixCls: () => {
// If Global prefixCls provided, use this
if (globalPrefixCls) {
return globalPrefixCls
}
// Fallback to default prefixCls
return getGlobalPrefixCls()
}
})
const ConfigProvider = defineComponent({
name: 'AConfigProvider',
props: {
...configProviderProps
},
setup(props, { slots }) {
// 依赖注入
const { shouldWrapSSR, iconPrefixCls } = useProviderConfigProvide(props)
const wrapSSR = useStyle(iconPrefixCls)
const memoTheme = computed(() => {
const { algorithm, token, ...rest } = props.theme || {}
const themeObj = algorithm && (!Array.isArray(algorithm) || algorithm.length > 0) ? createTheme(algorithm) : undefined
return {
...rest,
theme: themeObj,
token: {
...defaultSeedToken,
...token
}
}
})
return () => {
const { locale, theme } = props
const children = slots.default?.()
let childNode = shouldWrapSSR.value ? wrapSSR(children) : children
/**
* Form
*/
// const validateMessages = React.useMemo(
// () =>
// setValues(
// {},
// defaultLocale.Form?.defaultValidateMessages || {},
// memoedConfig.locale?.Form?.defaultValidateMessages || {},
// form?.validateMessages || {},
// ),
// [memoedConfig, form?.validateMessages],
// );
//
// if (Object.keys(validateMessages).length > 0) {
// childNode = <RcFormProvider validateMessages={validateMessages}>{children}</RcFormProvider>;
// }
/**
* 多语言实现部分
*/
if (locale) {
// 多语言部分
// childNode = <LocaleProvider locale={locale}>{childNode}</LocaleProvider>;
}
if (theme) {
childNode = (
<DesignTokenProviderContext
{...(memoTheme.value as DesignTokenConfig)}
token={memoTheme.value.token as any}
>
{childNode}
</DesignTokenProviderContext>
)
}
return childNode
}
}
})
ConfigProvider.install = (app: App) => {
app.component(ConfigProvider.name, ConfigProvider)
}
ConfigProvider.config = setGlobalConfig
ConfigProvider.useConfig = useConfig
export default ConfigProvider as typeof ConfigProvider & {
install(app: App): void
config: typeof setGlobalConfig
useConfig: typeof useConfig
}

View File

@ -0,0 +1 @@
# ConfigProvider 全局配置

View File

@ -0,0 +1,23 @@
import { useStyleRegister } from '@antd-tiny-vue/cssinjs'
import type { Ref } from 'vue'
import { computed } from 'vue'
import { resetIcon } from '../../style'
import { useToken } from '../../theme/internal'
const useStyle = (iconPrefixCls: Ref<string>) => {
const [theme, token] = useToken()
// Generate style for icons
const info = computed(() => ({ theme: theme.value, token: token.value, hashId: '', path: ['ant-design-icons', iconPrefixCls.value] }))
return useStyleRegister(info, () => [
{
[`.${iconPrefixCls.value}`]: {
...resetIcon(),
[`.${iconPrefixCls.value} .${iconPrefixCls.value}-icon`]: {
display: 'block'
}
}
}
])
}
export default useStyle

View File

@ -1,7 +1,15 @@
export {}
import type { App } from 'vue'
import * as components from './components'
import version from './version'
const aaa = () => {
// TODO
export default {
install(app: App) {
for (const componentsKey in components) {
const component = (components as any)[componentsKey]
if (component.install) {
app.use(component)
}
}
},
version
}
export default aaa

View File

@ -0,0 +1,142 @@
import {
anyType,
booleanType,
classNames,
createInjectionState,
filterEmpty,
isObject,
stringType
} from '@v-c/utils'
import type { Ref } from 'vue'
import { computed, defineComponent } from 'vue'
import type { DirectionType } from '../config-provider'
import type { SizeType } from '../config-provider/context'
import { useProviderConfigState } from '../config-provider/context'
import useStyle from './style'
const spaceCompactItem = (props: any) => {
return {
compactDirection: computed(() => props.compactDirection),
isFirstItem: computed(() => props.isFirstItem),
isLastItem: computed(() => props.isLastItem),
compactSize: computed(() => props.compactSize)
}
}
export const [useSpaceCompactProvider, useSpaceCompactInject] =
createInjectionState(spaceCompactItem)
export const useSpaceCompactItemState = ():
| ReturnType<typeof spaceCompactItem>
| undefined => useSpaceCompactInject()
export const useCompactItemContext = (
prefixCls: Ref<string>,
direction: Ref<DirectionType>
) => {
const compactItemContext = useSpaceCompactItemState()
const compactItemClassnames = computed(() => {
if (!compactItemContext) return ''
const { compactDirection, isFirstItem, isLastItem } = compactItemContext
const separator = compactDirection.value === 'vertical' ? '-vertical-' : '-'
return classNames({
[`${prefixCls.value}-compact${separator}item`]: true,
[`${prefixCls.value}-compact${separator}first-item`]: isFirstItem.value,
[`${prefixCls.value}-compact${separator}last-item`]: isLastItem.value,
[`${prefixCls.value}-compact${separator}item-rtl`]:
direction.value === 'rtl'
})
})
return {
compactSize: compactItemContext?.compactSize,
compactDirection: compactItemContext?.compactDirection,
compactItemClassnames
}
}
const CompactItem = defineComponent({
name: 'CompactItem',
inheritAttrs: false,
props: {
compactSize: anyType<SizeType>(),
compactDirection: stringType<'horizontal' | 'vertical'>(),
isFirstItem: booleanType(),
isLastItem: booleanType()
},
setup(props, { slots }) {
useSpaceCompactProvider(props)
return () => slots.default?.()
}
})
export const spaceCompactProps = {
prefixCls: stringType(),
size: anyType<SizeType>('middle'),
direction: stringType<'horizontal' | 'vertical'>(),
block: anyType<boolean>(),
rootClassName: stringType()
}
const Compact = defineComponent({
name: 'Compact',
inheritAttrs: false,
props: spaceCompactProps,
setup(props, { slots, attrs }) {
const { getPrefixCls, direction: directionConfig } =
useProviderConfigState()
const prefixCls = computed(() =>
getPrefixCls('space-compact', props.prefixCls)
)
const [wrapSSR, hashId] = useStyle(prefixCls)
const compactItemContext = useSpaceCompactItemState()
return () => {
const childNodes = filterEmpty(slots.default?.())
if (childNodes.length === 0) return null
const { rootClassName, size, direction } = props
const cls = classNames(
prefixCls.value,
hashId.value,
{
[`${prefixCls.value}-rtl`]: directionConfig.value === 'rtl',
[`${prefixCls.value}-block`]: props.block,
[`${prefixCls.value}-vertical`]: props.direction === 'vertical'
},
attrs.class,
rootClassName
)
const nodes = childNodes.map((child, index) => {
const key =
(isObject(child) && (child as any).key) ||
`${prefixCls.value}-item-${index}`
return (
<CompactItem
key={key}
compactSize={size}
compactDirection={direction}
isFirstItem={
(index === 0 && !compactItemContext) ||
compactItemContext?.isFirstItem.value
}
isLastItem={
index === childNodes.length - 1 &&
(!compactItemContext || compactItemContext?.isLastItem.value)
}
>
{child}
</CompactItem>
)
})
return wrapSSR(
<div {...attrs} class={cls}>
{nodes}
</div>
)
}
}
})
export default Compact

157
components/space/index.tsx Normal file
View File

@ -0,0 +1,157 @@
import {
anyType,
booleanType,
classNames,
createInjectionState,
filterEmpty,
isObject,
stringType,
vNodeType
} from '@v-c/utils'
import type { App, CSSProperties } from 'vue'
import { computed, defineComponent, shallowRef } from 'vue'
import type { SizeType } from '../config-provider/context'
import { useProviderConfigState } from '../config-provider/context'
import useFlexGapSupport from '../_util/hooks/flex-gap-support'
import useStyle from './style'
import Item from './item'
import Compact from './compact'
export type SpaceSize = SizeType | number
const spaceState = function ({ sizes, supportFlexGap, latestIndex }: any) {
return {
latestIndex: computed(() => latestIndex.value),
horizontalSize: computed(() => sizes[0]),
verticalSize: computed(() => sizes[1]),
supportFlexGap: computed(() => supportFlexGap.value)
}
}
export const [useSpaceProvider, useSpaceInject] =
createInjectionState(spaceState)
export const useSpaceContextState = () =>
useSpaceInject() ?? {
latestIndex: computed(() => 0),
horizontalSize: computed(() => 0),
verticalSize: computed(() => 0),
supportFlexGap: computed(() => false)
}
export const spaceProps = {
prefixCls: stringType(),
rootClassName: stringType(),
size: anyType<SizeType | [SpaceSize, SpaceSize]>('small'),
direction: anyType<'horizontal' | 'vertical'>('horizontal'),
align: stringType<'start' | 'end' | 'center' | 'baseline'>(),
split: vNodeType(),
wrap: booleanType(false)
}
const spaceSize = {
small: 8,
middle: 16,
large: 24
}
function getNumberSize(size: SpaceSize) {
return typeof size === 'string' ? spaceSize[size] : size || 0
}
const Space = defineComponent({
name: 'ASpace',
inheritAttrs: false,
props: spaceProps,
setup(props, { attrs, slots }) {
const { getPrefixCls, direction: directionConfig } =
useProviderConfigState()
const supportFlexGap = useFlexGapSupport()
const prefixCls = computed(() => getPrefixCls('space', props.prefixCls))
const [wrapSSR, hashId] = useStyle(prefixCls)
const sizes = computed<[SpaceSize, SpaceSize]>(() => {
const { size } = props
if (Array.isArray(size)) {
return size.map(getNumberSize) as [SpaceSize, SpaceSize]
}
return [getNumberSize(size), getNumberSize(size)] as [
SpaceSize,
SpaceSize
]
})
const latestIndex = shallowRef(0)
useSpaceProvider({ sizes, supportFlexGap, latestIndex })
return () => {
const { align, direction, rootClassName, split, wrap } = props
const childNodes = filterEmpty(slots.default?.() as any)
const mergedAlign =
align === undefined && direction === 'horizontal' ? 'center' : align
const cn = classNames(
prefixCls.value,
hashId.value,
`${prefixCls.value}-${direction}`,
{
[`${prefixCls.value}-rtl`]: directionConfig.value === 'rtl',
[`${prefixCls.value}-align-${mergedAlign}`]: mergedAlign
},
attrs.class,
rootClassName
)
const itemClassName = `${prefixCls.value}-item`
const marginDirection =
directionConfig.value === 'rtl' ? 'marginLeft' : 'marginRight'
const nodes = childNodes.map((child, i) => {
if (child !== null && child !== undefined) {
latestIndex.value = i
}
const key =
(isObject(child) && (child as any).key) || `${itemClassName}-${i}`
return (
<Item
class={itemClassName}
key={key}
direction={direction}
index={i}
marginDirection={marginDirection}
split={split}
wrap={wrap}
>
{child}
</Item>
)
})
// =========================== Render ===========================
if (childNodes.length === 0) {
return null
}
const gapStyle: CSSProperties = {}
if (wrap) {
gapStyle.flexWrap = 'wrap'
if (!supportFlexGap.value) {
gapStyle.marginBottom = `-${sizes.value[1]}px`
}
}
if (supportFlexGap.value) {
gapStyle.columnGap = `${sizes.value[0]}px`
gapStyle.rowGap = `${sizes.value[1]}px`
}
return wrapSSR(
<div {...attrs} class={cn} style={[gapStyle, (attrs as any).style]}>
{nodes}
</div>
)
}
}
})
Space.install = function (app: App) {
app.component('ASpace', Space)
}
Space.Compact = Compact
export default Space as typeof Space &
Plugin & {
readonly Compact: typeof Compact
}

74
components/space/item.tsx Normal file
View File

@ -0,0 +1,74 @@
import {
booleanType,
filterEmpty,
numberType,
someType,
stringType,
vNodeType
} from '@v-c/utils'
import type { CSSProperties, ExtractPropTypes, VNodeChild } from 'vue'
import { defineComponent } from 'vue'
import { useSpaceContextState } from './index'
export const itemProps = {
className: stringType(),
children: vNodeType(),
index: numberType(),
direction: stringType<'horizontal' | 'vertical'>(),
marginDirection: stringType<'marginLeft' | 'marginRight'>(),
split: someType<string | (() => VNodeChild) | VNodeChild>([
String,
Function,
Object
]),
wrap: booleanType()
}
export type ItemProps = ExtractPropTypes<typeof itemProps>
const Item = defineComponent({
name: 'VcSpaceItem',
props: itemProps,
setup(props, { attrs, slots }) {
const { supportFlexGap, latestIndex, verticalSize, horizontalSize } =
useSpaceContextState()
return () => {
const { direction, index, marginDirection, split, wrap } = props
const children = slots.default?.()
if (!children || filterEmpty(children).length === 0) {
return null
}
let style: CSSProperties = {}
if (!supportFlexGap.value) {
if (direction === 'vertical') {
if (index < latestIndex.value) {
style.marginBottom = `${horizontalSize.value / (split ? 2 : 1)}px`
}
} else {
style = {
...(index < latestIndex.value &&
({
[marginDirection]: `${horizontalSize.value / (split ? 2 : 1)}px`
} as CSSProperties)),
...(wrap && { paddingBottom: `${verticalSize.value}px` })
}
}
}
return (
<>
<div class={attrs.class} style={style}>
{children}
</div>
{index < latestIndex.value && split && (
<span class={`${attrs.class}-split`} style={style}>
{split}
</span>
)}
</>
)
}
}
})
export default Item

View File

@ -0,0 +1,29 @@
import type { FullToken, GenerateStyle } from '../../theme/internal'
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {
// Component token here
}
interface SpaceToken extends FullToken<'Space'> {
// Custom token here
}
const genSpaceCompactStyle: GenerateStyle<SpaceToken> = (token) => {
const { componentCls } = token
return {
[componentCls]: {
display: 'inline-flex',
'&-block': {
display: 'flex',
width: '100%'
},
'&-vertical': {
flexDirection: 'column'
}
}
}
}
// ============================== Export ==============================
export default genSpaceCompactStyle

View File

@ -0,0 +1,54 @@
import type { FullToken, GenerateStyle } from '../../theme/internal'
import { genComponentStyleHook } from '../../theme/internal'
import genSpaceCompactStyle from './compact'
/** Component only token. Which will handle additional calculation of alias token */
export interface ComponentToken {
// Component token here
}
interface SpaceToken extends FullToken<'Space'> {
// Custom token here
}
const genSpaceStyle: GenerateStyle<SpaceToken> = (token) => {
const { componentCls } = token
return {
[componentCls]: {
display: 'inline-flex',
'&-rtl': {
direction: 'rtl'
},
'&-vertical': {
flexDirection: 'column'
},
'&-align': {
flexDirection: 'column',
'&-center': {
alignItems: 'center'
},
'&-start': {
alignItems: 'flex-start'
},
'&-end': {
alignItems: 'flex-end'
},
'&-baseline': {
alignItems: 'baseline'
}
},
[`${componentCls}-item`]: {
'&:empty': {
display: 'none'
}
}
}
}
}
// ============================== Export ==============================
export default genComponentStyleHook('Space', (token) => [
genSpaceStyle(token),
genSpaceCompactStyle(token)
])

View File

@ -0,0 +1,56 @@
/* eslint-disable import/prefer-default-export */
import type { CSSInterpolation, CSSObject } from '@antd-tiny-vue/cssinjs'
import type { DerivativeToken, FullToken } from '../theme/internal'
import type { OverrideComponent } from '../theme/util/genComponentStyleHook'
function compactItemVerticalBorder(token: DerivativeToken, parentCls: string): CSSObject {
return {
// border collapse
[`&-item:not(${parentCls}-last-item)`]: {
marginBottom: -token.lineWidth
},
'&-item': {
'&:hover,&:focus,&:active': {
zIndex: 2
},
'&[disabled]': {
zIndex: 0
}
}
}
}
function compactItemBorderVerticalRadius(prefixCls: string, parentCls: string): CSSObject {
return {
[`&-item:not(${parentCls}-first-item):not(${parentCls}-last-item)`]: {
borderRadius: 0
},
[`&-item${parentCls}-first-item:not(${parentCls}-last-item)`]: {
[`&, &${prefixCls}-sm, &${prefixCls}-lg`]: {
borderEndEndRadius: 0,
borderEndStartRadius: 0
}
},
[`&-item${parentCls}-last-item:not(${parentCls}-first-item)`]: {
[`&, &${prefixCls}-sm, &${prefixCls}-lg`]: {
borderStartStartRadius: 0,
borderStartEndRadius: 0
}
}
}
}
export function genCompactItemVerticalStyle<T extends OverrideComponent>(token: FullToken<T>): CSSInterpolation {
const compactCls = `${token.componentCls}-compact-vertical`
return {
[compactCls]: {
...compactItemVerticalBorder(token, compactCls),
...compactItemBorderVerticalRadius(token.componentCls, compactCls)
}
}
}

View File

@ -0,0 +1,90 @@
/* eslint-disable import/prefer-default-export */
import type { CSSInterpolation, CSSObject } from '@antd-tiny-vue/cssinjs'
import type { DerivativeToken, FullToken } from '../theme/internal'
import type { OverrideComponent } from '../theme/util/genComponentStyleHook'
interface CompactItemOptions {
focus?: boolean
/**
* Some component borders are implemented on child elements
* like `Select`
*/
borderElCls?: string
/**
* Some components have special `focus` className especially with popovers
* like `Select` and `DatePicker`
*/
focusElCls?: string
}
// handle border collapse
function compactItemBorder(token: DerivativeToken, parentCls: string, options: CompactItemOptions): CSSObject {
const { focusElCls, focus, borderElCls } = options
const childCombinator = borderElCls ? '> *' : ''
const hoverEffects = ['hover', focus ? 'focus' : null, 'active']
.filter(Boolean)
.map(n => `&:${n} ${childCombinator}`)
.join(',')
return {
[`&-item:not(${parentCls}-last-item)`]: {
marginInlineEnd: -token.lineWidth
},
'&-item': {
[hoverEffects]: {
zIndex: 2
},
...(focusElCls
? {
[`&${focusElCls}`]: {
zIndex: 2
}
}
: {}),
[`&[disabled] ${childCombinator}`]: {
zIndex: 0
}
}
}
}
// handle border-radius
function compactItemBorderRadius(prefixCls: string, parentCls: string, options: CompactItemOptions): CSSObject {
const { borderElCls } = options
const childCombinator = borderElCls ? `> ${borderElCls}` : ''
return {
[`&-item:not(${parentCls}-first-item):not(${parentCls}-last-item) ${childCombinator}`]: {
borderRadius: 0
},
[`&-item:not(${parentCls}-last-item)${parentCls}-first-item`]: {
[`& ${childCombinator}, &${prefixCls}-sm ${childCombinator}, &${prefixCls}-lg ${childCombinator}`]: {
borderStartEndRadius: 0,
borderEndEndRadius: 0
}
},
[`&-item:not(${parentCls}-first-item)${parentCls}-last-item`]: {
[`& ${childCombinator}, &${prefixCls}-sm ${childCombinator}, &${prefixCls}-lg ${childCombinator}`]: {
borderStartStartRadius: 0,
borderEndStartRadius: 0
}
}
}
}
export function genCompactItemStyle<T extends OverrideComponent>(token: FullToken<T>, options: CompactItemOptions = { focus: true }): CSSInterpolation {
const { componentCls } = token
const compactCls = `${componentCls}-compact`
return {
[compactCls]: {
...compactItemBorder(token, compactCls, options),
...compactItemBorderRadius(componentCls, compactCls, options)
}
}
}

139
components/style/index.ts Normal file
View File

@ -0,0 +1,139 @@
/* eslint-disable import/prefer-default-export */
import type { CSSObject } from '@antd-tiny-vue/cssinjs'
import type { DerivativeToken } from '../theme/internal'
export { operationUnit } from './operationUnit'
export { roundedArrow } from './roundedArrow'
export { genPresetColor } from './presetColor'
export const textEllipsis: CSSObject = {
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis'
}
export const resetComponent = (token: DerivativeToken): CSSObject => ({
boxSizing: 'border-box',
margin: 0,
padding: 0,
color: token.colorText,
fontSize: token.fontSize,
// font-variant: @font-variant-base;
lineHeight: token.lineHeight,
listStyle: 'none',
// font-feature-settings: @font-feature-settings-base;
fontFamily: token.fontFamily
})
export const resetIcon = (): CSSObject => ({
display: 'inline-flex',
alignItems: 'center',
color: 'inherit',
fontStyle: 'normal',
lineHeight: 0,
textAlign: 'center',
textTransform: 'none',
// for SVG icon, see https://blog.prototypr.io/align-svg-icons-to-text-and-say-goodbye-to-font-icons-d44b3d7b26b4
verticalAlign: '-0.125em',
textRendering: 'optimizeLegibility',
'-webkit-font-smoothing': 'antialiased',
'-moz-osx-font-smoothing': 'grayscale',
'> *': {
lineHeight: 1
},
svg: {
display: 'inline-block'
}
})
export const clearFix = (): CSSObject => ({
// https://github.com/ant-design/ant-design/issues/21301#issuecomment-583955229
'&::before': {
display: 'table',
content: '""'
},
'&::after': {
// https://github.com/ant-design/ant-design/issues/21864
display: 'table',
clear: 'both',
content: '""'
}
})
export const genLinkStyle = (token: DerivativeToken): CSSObject => ({
a: {
color: token.colorLink,
textDecoration: token.linkDecoration,
backgroundColor: 'transparent', // remove the gray background on active links in IE 10.
outline: 'none',
cursor: 'pointer',
transition: `color ${token.motionDurationSlow}`,
'-webkit-text-decoration-skip': 'objects', // remove gaps in links underline in iOS 8+ and Safari 8+.
'&:hover': {
color: token.colorLinkHover
},
'&:active': {
color: token.colorLinkActive
},
[`&:active,
&:hover`]: {
textDecoration: token.linkHoverDecoration,
outline: 0
},
// https://github.com/ant-design/ant-design/issues/22503
'&:focus': {
textDecoration: token.linkFocusDecoration,
outline: 0
},
'&[disabled]': {
color: token.colorTextDisabled,
cursor: 'not-allowed'
}
}
})
export const genCommonStyle = (token: DerivativeToken, componentPrefixCls: string): CSSObject => {
const { fontFamily, fontSize } = token
const rootPrefixSelector = `[class^="${componentPrefixCls}"], [class*=" ${componentPrefixCls}"]`
return {
[rootPrefixSelector]: {
fontFamily,
fontSize,
boxSizing: 'border-box',
'&::before, &::after': {
boxSizing: 'border-box'
},
[rootPrefixSelector]: {
boxSizing: 'border-box',
'&::before, &::after': {
boxSizing: 'border-box'
}
}
}
}
}
export const genFocusOutline = (token: DerivativeToken): CSSObject => ({
outline: `${token.lineWidth * 4}px solid ${token.colorPrimaryBorder}`,
outlineOffset: 1,
transition: 'outline-offset 0s, outline 0s'
})
export const genFocusStyle = (token: DerivativeToken): CSSObject => ({
'&:focus-visible': {
...genFocusOutline(token)
}
})

View File

@ -0,0 +1,26 @@
import type { AliasToken, GenerateStyle } from '../../theme/internal'
import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook'
const genCollapseMotion: GenerateStyle<TokenWithCommonCls<AliasToken>> = (
token
) => ({
[token.componentCls]: {
// For common/openAnimation
[`${token.antCls}-motion-collapse-legacy`]: {
overflow: 'hidden',
'&-active': {
transition: `height ${token.motionDurationMid} ${token.motionEaseInOut},
opacity ${token.motionDurationMid} ${token.motionEaseInOut} !important`
}
},
[`${token.antCls}-motion-collapse`]: {
overflow: 'hidden',
transition: `height ${token.motionDurationMid} ${token.motionEaseInOut},
opacity ${token.motionDurationMid} ${token.motionEaseInOut} !important`
}
}
})
export default genCollapseMotion

View File

@ -0,0 +1,46 @@
import type { CSSInterpolation } from '@antd-tiny-vue/cssinjs'
import { Keyframes } from '@antd-tiny-vue/cssinjs'
import type { AliasToken } from '../../theme/internal'
import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook'
import { initMotion } from './motion'
export const fadeIn = new Keyframes('antFadeIn', {
'0%': {
opacity: 0
},
'100%': {
opacity: 1
}
})
export const fadeOut = new Keyframes('antFadeOut', {
'0%': {
opacity: 1
},
'100%': {
opacity: 0
}
})
export const initFadeMotion = (token: TokenWithCommonCls<AliasToken>, sameLevel = false): CSSInterpolation => {
const { antCls } = token
const motionCls = `${antCls}-fade`
const sameLevelPrefix = sameLevel ? '&' : ''
return [
initMotion(motionCls, fadeIn, fadeOut, token.motionDurationMid, sameLevel),
{
[`
${sameLevelPrefix}${motionCls}-enter,
${sameLevelPrefix}${motionCls}-appear
`]: {
opacity: 0,
animationTimingFunction: 'linear'
},
[`${sameLevelPrefix}${motionCls}-leave`]: {
animationTimingFunction: 'linear'
}
}
]
}

View File

@ -0,0 +1,44 @@
import { fadeIn, fadeOut, initFadeMotion } from './fade'
import { initMoveMotion, moveDownIn, moveDownOut, moveLeftIn, moveLeftOut, moveRightIn, moveRightOut, moveUpIn, moveUpOut } from './move'
import { initSlideMotion, slideDownIn, slideDownOut, slideLeftIn, slideLeftOut, slideRightIn, slideRightOut, slideUpIn, slideUpOut } from './slide'
import { initZoomMotion, zoomBigIn, zoomBigOut, zoomDownIn, zoomDownOut, zoomIn, zoomLeftIn, zoomLeftOut, zoomOut, zoomRightIn, zoomRightOut, zoomUpIn, zoomUpOut } from './zoom'
import genCollapseMotion from './collapse'
export {
initSlideMotion,
slideUpIn,
slideUpOut,
slideDownIn,
slideDownOut,
slideLeftIn,
slideLeftOut,
slideRightIn,
slideRightOut,
zoomOut,
zoomIn,
zoomBigIn,
zoomLeftOut,
zoomBigOut,
zoomLeftIn,
zoomRightIn,
zoomUpIn,
zoomRightOut,
zoomUpOut,
zoomDownIn,
zoomDownOut,
initZoomMotion,
fadeIn,
fadeOut,
initFadeMotion,
moveRightOut,
moveRightIn,
moveLeftOut,
moveLeftIn,
moveDownOut,
moveDownIn,
moveUpIn,
moveUpOut,
initMoveMotion,
genCollapseMotion
}

View File

@ -0,0 +1,46 @@
/* eslint-disable import/prefer-default-export */
import type { CSSObject, Keyframes } from '@antd-tiny-vue/cssinjs'
const initMotionCommon = (duration: string): CSSObject => ({
animationDuration: duration,
animationFillMode: 'both'
})
// FIXME: origin less code seems same as initMotionCommon. Maybe we can safe remove
const initMotionCommonLeave = (duration: string): CSSObject => ({
animationDuration: duration,
animationFillMode: 'both'
})
export const initMotion = (motionCls: string, inKeyframes: Keyframes, outKeyframes: Keyframes, duration: string, sameLevel = false): CSSObject => {
const sameLevelPrefix = sameLevel ? '&' : ''
return {
[`
${sameLevelPrefix}${motionCls}-enter,
${sameLevelPrefix}${motionCls}-appear
`]: {
...initMotionCommon(duration),
animationPlayState: 'paused'
},
[`${sameLevelPrefix}${motionCls}-leave`]: {
...initMotionCommonLeave(duration),
animationPlayState: 'paused'
},
[`
${sameLevelPrefix}${motionCls}-enter${motionCls}-enter-active,
${sameLevelPrefix}${motionCls}-appear${motionCls}-appear-active
`]: {
animationName: inKeyframes,
animationPlayState: 'running'
},
[`${sameLevelPrefix}${motionCls}-leave${motionCls}-leave-active`]: {
animationName: outKeyframes,
animationPlayState: 'running',
pointerEvents: 'none'
}
}
}

View File

@ -0,0 +1,160 @@
import type { CSSInterpolation } from '@antd-tiny-vue/cssinjs'
import { Keyframes } from '@antd-tiny-vue/cssinjs'
import type { AliasToken } from '../../theme/internal'
import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook'
import { initMotion } from './motion'
export const moveDownIn = new Keyframes('antMoveDownIn', {
'0%': {
transform: 'translate3d(0, 100%, 0)',
transformOrigin: '0 0',
opacity: 0
},
'100%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1
}
})
export const moveDownOut = new Keyframes('antMoveDownOut', {
'0%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1
},
'100%': {
transform: 'translate3d(0, 100%, 0)',
transformOrigin: '0 0',
opacity: 0
}
})
export const moveLeftIn = new Keyframes('antMoveLeftIn', {
'0%': {
transform: 'translate3d(-100%, 0, 0)',
transformOrigin: '0 0',
opacity: 0
},
'100%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1
}
})
export const moveLeftOut = new Keyframes('antMoveLeftOut', {
'0%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1
},
'100%': {
transform: 'translate3d(-100%, 0, 0)',
transformOrigin: '0 0',
opacity: 0
}
})
export const moveRightIn = new Keyframes('antMoveRightIn', {
'0%': {
transform: 'translate3d(100%, 0, 0)',
transformOrigin: '0 0',
opacity: 0
},
'100%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1
}
})
export const moveRightOut = new Keyframes('antMoveRightOut', {
'0%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1
},
'100%': {
transform: 'translate3d(100%, 0, 0)',
transformOrigin: '0 0',
opacity: 0
}
})
export const moveUpIn = new Keyframes('antMoveUpIn', {
'0%': {
transform: 'translate3d(0, -100%, 0)',
transformOrigin: '0 0',
opacity: 0
},
'100%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1
}
})
export const moveUpOut = new Keyframes('antMoveUpOut', {
'0%': {
transform: 'translate3d(0, 0, 0)',
transformOrigin: '0 0',
opacity: 1
},
'100%': {
transform: 'translate3d(0, -100%, 0)',
transformOrigin: '0 0',
opacity: 0
}
})
type MoveMotionTypes = 'move-up' | 'move-down' | 'move-left' | 'move-right'
const moveMotion: Record<MoveMotionTypes, { inKeyframes: Keyframes; outKeyframes: Keyframes }> = {
'move-up': {
inKeyframes: moveUpIn,
outKeyframes: moveUpOut
},
'move-down': {
inKeyframes: moveDownIn,
outKeyframes: moveDownOut
},
'move-left': {
inKeyframes: moveLeftIn,
outKeyframes: moveLeftOut
},
'move-right': {
inKeyframes: moveRightIn,
outKeyframes: moveRightOut
}
}
export const initMoveMotion = (token: TokenWithCommonCls<AliasToken>, motionName: MoveMotionTypes): CSSInterpolation => {
const { antCls } = token
const motionCls = `${antCls}-${motionName}`
const { inKeyframes, outKeyframes } = moveMotion[motionName]
return [
initMotion(motionCls, inKeyframes, outKeyframes, token.motionDurationMid),
{
[`
${motionCls}-enter,
${motionCls}-appear
`]: {
opacity: 0,
animationTimingFunction: token.motionEaseOutCirc
},
[`${motionCls}-leave`]: {
animationTimingFunction: token.motionEaseInOutCirc
}
}
]
}

View File

@ -0,0 +1,163 @@
import type { CSSInterpolation } from '@antd-tiny-vue/cssinjs'
import { Keyframes } from '@antd-tiny-vue/cssinjs'
import type { AliasToken } from '../../theme/internal'
import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook'
import { initMotion } from './motion'
export const slideUpIn = new Keyframes('antSlideUpIn', {
'0%': {
transform: 'scaleY(0.8)',
transformOrigin: '0% 0%',
opacity: 0
},
'100%': {
transform: 'scaleY(1)',
transformOrigin: '0% 0%',
opacity: 1
}
})
export const slideUpOut = new Keyframes('antSlideUpOut', {
'0%': {
transform: 'scaleY(1)',
transformOrigin: '0% 0%',
opacity: 1
},
'100%': {
transform: 'scaleY(0.8)',
transformOrigin: '0% 0%',
opacity: 0
}
})
export const slideDownIn = new Keyframes('antSlideDownIn', {
'0%': {
transform: 'scaleY(0.8)',
transformOrigin: '100% 100%',
opacity: 0
},
'100%': {
transform: 'scaleY(1)',
transformOrigin: '100% 100%',
opacity: 1
}
})
export const slideDownOut = new Keyframes('antSlideDownOut', {
'0%': {
transform: 'scaleY(1)',
transformOrigin: '100% 100%',
opacity: 1
},
'100%': {
transform: 'scaleY(0.8)',
transformOrigin: '100% 100%',
opacity: 0
}
})
export const slideLeftIn = new Keyframes('antSlideLeftIn', {
'0%': {
transform: 'scaleX(0.8)',
transformOrigin: '0% 0%',
opacity: 0
},
'100%': {
transform: 'scaleX(1)',
transformOrigin: '0% 0%',
opacity: 1
}
})
export const slideLeftOut = new Keyframes('antSlideLeftOut', {
'0%': {
transform: 'scaleX(1)',
transformOrigin: '0% 0%',
opacity: 1
},
'100%': {
transform: 'scaleX(0.8)',
transformOrigin: '0% 0%',
opacity: 0
}
})
export const slideRightIn = new Keyframes('antSlideRightIn', {
'0%': {
transform: 'scaleX(0.8)',
transformOrigin: '100% 0%',
opacity: 0
},
'100%': {
transform: 'scaleX(1)',
transformOrigin: '100% 0%',
opacity: 1
}
})
export const slideRightOut = new Keyframes('antSlideRightOut', {
'0%': {
transform: 'scaleX(1)',
transformOrigin: '100% 0%',
opacity: 1
},
'100%': {
transform: 'scaleX(0.8)',
transformOrigin: '100% 0%',
opacity: 0
}
})
type SlideMotionTypes = 'slide-up' | 'slide-down' | 'slide-left' | 'slide-right'
const slideMotion: Record<SlideMotionTypes, { inKeyframes: Keyframes; outKeyframes: Keyframes }> = {
'slide-up': {
inKeyframes: slideUpIn,
outKeyframes: slideUpOut
},
'slide-down': {
inKeyframes: slideDownIn,
outKeyframes: slideDownOut
},
'slide-left': {
inKeyframes: slideLeftIn,
outKeyframes: slideLeftOut
},
'slide-right': {
inKeyframes: slideRightIn,
outKeyframes: slideRightOut
}
}
export const initSlideMotion = (token: TokenWithCommonCls<AliasToken>, motionName: SlideMotionTypes): CSSInterpolation => {
const { antCls } = token
const motionCls = `${antCls}-${motionName}`
const { inKeyframes, outKeyframes } = slideMotion[motionName]
return [
initMotion(motionCls, inKeyframes, outKeyframes, token.motionDurationMid),
{
[`
${motionCls}-enter,
${motionCls}-appear
`]: {
transform: 'scale(0)',
transformOrigin: '0% 0%',
opacity: 0,
animationTimingFunction: token.motionEaseOutQuint
},
[`${motionCls}-leave`]: {
animationTimingFunction: token.motionEaseInQuint
}
}
]
}

View File

@ -0,0 +1,215 @@
import type { CSSInterpolation } from '@antd-tiny-vue/cssinjs'
import { Keyframes } from '@antd-tiny-vue/cssinjs'
import type { AliasToken } from '../../theme/internal'
import type { TokenWithCommonCls } from '../../theme/util/genComponentStyleHook'
import { initMotion } from './motion'
export const zoomIn = new Keyframes('antZoomIn', {
'0%': {
transform: 'scale(0.2)',
opacity: 0
},
'100%': {
transform: 'scale(1)',
opacity: 1
}
})
export const zoomOut = new Keyframes('antZoomOut', {
'0%': {
transform: 'scale(1)'
},
'100%': {
transform: 'scale(0.2)',
opacity: 0
}
})
export const zoomBigIn = new Keyframes('antZoomBigIn', {
'0%': {
transform: 'scale(0.8)',
opacity: 0
},
'100%': {
transform: 'scale(1)',
opacity: 1
}
})
export const zoomBigOut = new Keyframes('antZoomBigOut', {
'0%': {
transform: 'scale(1)'
},
'100%': {
transform: 'scale(0.8)',
opacity: 0
}
})
export const zoomUpIn = new Keyframes('antZoomUpIn', {
'0%': {
transform: 'scale(0.8)',
transformOrigin: '50% 0%',
opacity: 0
},
'100%': {
transform: 'scale(1)',
transformOrigin: '50% 0%'
}
})
export const zoomUpOut = new Keyframes('antZoomUpOut', {
'0%': {
transform: 'scale(1)',
transformOrigin: '50% 0%'
},
'100%': {
transform: 'scale(0.8)',
transformOrigin: '50% 0%',
opacity: 0
}
})
export const zoomLeftIn = new Keyframes('antZoomLeftIn', {
'0%': {
transform: 'scale(0.8)',
transformOrigin: '0% 50%',
opacity: 0
},
'100%': {
transform: 'scale(1)',
transformOrigin: '0% 50%'
}
})
export const zoomLeftOut = new Keyframes('antZoomLeftOut', {
'0%': {
transform: 'scale(1)',
transformOrigin: '0% 50%'
},
'100%': {
transform: 'scale(0.8)',
transformOrigin: '0% 50%',
opacity: 0
}
})
export const zoomRightIn = new Keyframes('antZoomRightIn', {
'0%': {
transform: 'scale(0.8)',
transformOrigin: '100% 50%',
opacity: 0
},
'100%': {
transform: 'scale(1)',
transformOrigin: '100% 50%'
}
})
export const zoomRightOut = new Keyframes('antZoomRightOut', {
'0%': {
transform: 'scale(1)',
transformOrigin: '100% 50%'
},
'100%': {
transform: 'scale(0.8)',
transformOrigin: '100% 50%',
opacity: 0
}
})
export const zoomDownIn = new Keyframes('antZoomDownIn', {
'0%': {
transform: 'scale(0.8)',
transformOrigin: '50% 100%',
opacity: 0
},
'100%': {
transform: 'scale(1)',
transformOrigin: '50% 100%'
}
})
export const zoomDownOut = new Keyframes('antZoomDownOut', {
'0%': {
transform: 'scale(1)',
transformOrigin: '50% 100%'
},
'100%': {
transform: 'scale(0.8)',
transformOrigin: '50% 100%',
opacity: 0
}
})
type ZoomMotionTypes = 'zoom' | 'zoom-big' | 'zoom-big-fast' | 'zoom-left' | 'zoom-right' | 'zoom-up' | 'zoom-down'
const zoomMotion: Record<ZoomMotionTypes, { inKeyframes: Keyframes; outKeyframes: Keyframes }> = {
zoom: {
inKeyframes: zoomIn,
outKeyframes: zoomOut
},
'zoom-big': {
inKeyframes: zoomBigIn,
outKeyframes: zoomBigOut
},
'zoom-big-fast': {
inKeyframes: zoomBigIn,
outKeyframes: zoomBigOut
},
'zoom-left': {
inKeyframes: zoomLeftIn,
outKeyframes: zoomLeftOut
},
'zoom-right': {
inKeyframes: zoomRightIn,
outKeyframes: zoomRightOut
},
'zoom-up': {
inKeyframes: zoomUpIn,
outKeyframes: zoomUpOut
},
'zoom-down': {
inKeyframes: zoomDownIn,
outKeyframes: zoomDownOut
}
}
export const initZoomMotion = (token: TokenWithCommonCls<AliasToken>, motionName: ZoomMotionTypes): CSSInterpolation => {
const { antCls } = token
const motionCls = `${antCls}-${motionName}`
const { inKeyframes, outKeyframes } = zoomMotion[motionName]
return [
initMotion(motionCls, inKeyframes, outKeyframes, motionName === 'zoom-big-fast' ? token.motionDurationFast : token.motionDurationMid),
{
[`
${motionCls}-enter,
${motionCls}-appear
`]: {
transform: 'scale(0)',
opacity: 0,
animationTimingFunction: token.motionEaseOutCirc,
'&-prepare': {
transform: 'none'
}
},
[`${motionCls}-leave`]: {
animationTimingFunction: token.motionEaseInOutCirc
}
}
]
}

View File

@ -0,0 +1,21 @@
import type { CSSObject } from '@antd-tiny-vue/cssinjs'
import type { DerivativeToken } from '../theme/internal'
// eslint-disable-next-line import/prefer-default-export
export const operationUnit = (token: DerivativeToken): CSSObject => ({
// FIXME: This use link but is a operation unit. Seems should be a colorPrimary.
// And Typography use this to generate link style which should not do this.
color: token.colorLink,
textDecoration: 'none',
outline: 'none',
cursor: 'pointer',
transition: `color ${token.motionDurationSlow}`,
'&:focus, &:hover': {
color: token.colorLinkHover
},
'&:active': {
color: token.colorLinkActive
}
})

View File

@ -0,0 +1,191 @@
import type { CSSInterpolation, CSSObject } from '@antd-tiny-vue/cssinjs'
import type { AliasToken } from '../theme/internal'
import type { TokenWithCommonCls } from '../theme/util/genComponentStyleHook'
import { roundedArrow } from './roundedArrow'
export const MAX_VERTICAL_CONTENT_RADIUS = 8
export function getArrowOffset(options: { contentRadius: number; limitVerticalRadius?: boolean }) {
const maxVerticalContentRadius = MAX_VERTICAL_CONTENT_RADIUS
const { contentRadius, limitVerticalRadius } = options
const dropdownArrowOffset = contentRadius > 12 ? contentRadius + 2 : 12
const dropdownArrowOffsetVertical = limitVerticalRadius ? maxVerticalContentRadius : dropdownArrowOffset
return { dropdownArrowOffset, dropdownArrowOffsetVertical }
}
function isInject(valid: boolean, code: CSSObject): CSSObject {
if (!valid) return {}
return code
}
export default function getArrowStyle<Token extends TokenWithCommonCls<AliasToken>>(
token: Token,
options: {
colorBg: string
showArrowCls?: string
contentRadius?: number
limitVerticalRadius?: boolean
arrowDistance?: number
arrowPlacement?: {
left?: boolean
right?: boolean
top?: boolean
bottom?: boolean
}
}
): CSSInterpolation {
const { componentCls, sizePopupArrow, borderRadiusXS, borderRadiusOuter, boxShadowPopoverArrow } = token
const {
colorBg,
contentRadius = token.borderRadiusLG,
limitVerticalRadius,
arrowDistance = 0,
arrowPlacement = {
left: true,
right: true,
top: true,
bottom: true
}
} = options
const { dropdownArrowOffsetVertical, dropdownArrowOffset } = getArrowOffset({
contentRadius,
limitVerticalRadius
})
return {
[componentCls]: {
// ============================ Basic ============================
[`${componentCls}-arrow`]: [
{
position: 'absolute',
zIndex: 1, // lift it up so the menu wouldn't cask shadow on it
display: 'block',
...roundedArrow(sizePopupArrow, borderRadiusXS, borderRadiusOuter, colorBg, boxShadowPopoverArrow),
'&:before': {
background: colorBg
}
}
],
// ========================== Placement ==========================
// Here handle the arrow position and rotate stuff
// >>>>> Top
...isInject(!!arrowPlacement.top, {
[[`&-placement-top ${componentCls}-arrow`, `&-placement-topLeft ${componentCls}-arrow`, `&-placement-topRight ${componentCls}-arrow`].join(',')]: {
bottom: arrowDistance,
transform: 'translateY(100%) rotate(180deg)'
},
[`&-placement-top ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: '50%'
},
transform: 'translateX(-50%) translateY(100%) rotate(180deg)'
},
[`&-placement-topLeft ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: dropdownArrowOffset
}
},
[`&-placement-topRight ${componentCls}-arrow`]: {
right: {
_skip_check_: true,
value: dropdownArrowOffset
}
}
}),
// >>>>> Bottom
...isInject(!!arrowPlacement.bottom, {
[[`&-placement-bottom ${componentCls}-arrow`, `&-placement-bottomLeft ${componentCls}-arrow`, `&-placement-bottomRight ${componentCls}-arrow`].join(',')]: {
top: arrowDistance,
transform: `translateY(-100%)`
},
[`&-placement-bottom ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: '50%'
},
transform: `translateX(-50%) translateY(-100%)`
},
[`&-placement-bottomLeft ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: dropdownArrowOffset
}
},
[`&-placement-bottomRight ${componentCls}-arrow`]: {
right: {
_skip_check_: true,
value: dropdownArrowOffset
}
}
}),
// >>>>> Left
...isInject(!!arrowPlacement.left, {
[[`&-placement-left ${componentCls}-arrow`, `&-placement-leftTop ${componentCls}-arrow`, `&-placement-leftBottom ${componentCls}-arrow`].join(',')]: {
right: {
_skip_check_: true,
value: arrowDistance
},
transform: 'translateX(100%) rotate(90deg)'
},
[`&-placement-left ${componentCls}-arrow`]: {
top: {
_skip_check_: true,
value: '50%'
},
transform: 'translateY(-50%) translateX(100%) rotate(90deg)'
},
[`&-placement-leftTop ${componentCls}-arrow`]: {
top: dropdownArrowOffsetVertical
},
[`&-placement-leftBottom ${componentCls}-arrow`]: {
bottom: dropdownArrowOffsetVertical
}
}),
// >>>>> Right
...isInject(!!arrowPlacement.right, {
[[`&-placement-right ${componentCls}-arrow`, `&-placement-rightTop ${componentCls}-arrow`, `&-placement-rightBottom ${componentCls}-arrow`].join(',')]: {
left: {
_skip_check_: true,
value: arrowDistance
},
transform: 'translateX(-100%) rotate(-90deg)'
},
[`&-placement-right ${componentCls}-arrow`]: {
top: {
_skip_check_: true,
value: '50%'
},
transform: 'translateY(-50%) translateX(-100%) rotate(-90deg)'
},
[`&-placement-rightTop ${componentCls}-arrow`]: {
top: dropdownArrowOffsetVertical
},
[`&-placement-rightBottom ${componentCls}-arrow`]: {
bottom: dropdownArrowOffsetVertical
}
})
}
}
}

View File

@ -0,0 +1,32 @@
/* eslint-disable import/prefer-default-export */
import type { CSSObject } from '@antd-tiny-vue/cssinjs'
import type { AliasToken, PresetColorKey } from '../theme/internal'
import { PresetColors } from '../theme/internal'
import type { TokenWithCommonCls } from '../theme/util/genComponentStyleHook'
interface CalcColor {
/** token[`${colorKey}-1`] */
lightColor: string
/** token[`${colorKey}-3`] */
lightBorderColor: string
/** token[`${colorKey}-6`] */
darkColor: string
/** token[`${colorKey}-7`] */
textColor: string
}
type GenCSS = (colorKey: PresetColorKey, calcColor: CalcColor) => CSSObject
export function genPresetColor<Token extends TokenWithCommonCls<AliasToken>>(token: Token, genCss: GenCSS): CSSObject {
return PresetColors.reduce((prev: CSSObject, colorKey: PresetColorKey) => {
const lightColor = token[`${colorKey}-1`]
const lightBorderColor = token[`${colorKey}-3`]
const darkColor = token[`${colorKey}-6`]
const textColor = token[`${colorKey}-7`]
return {
...prev,
...genCss(colorKey, { lightColor, lightBorderColor, darkColor, textColor })
}
}, {} as CSSObject)
}

253
components/style/reset.css Normal file
View File

@ -0,0 +1,253 @@
/* stylelint-disable */
html,
body {
width: 100%;
height: 100%;
}
input::-ms-clear,
input::-ms-reveal {
display: none;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-ms-overflow-style: scrollbar;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
@-ms-viewport {
width: device-width;
}
body {
margin: 0;
}
[tabindex='-1']:focus {
outline: none;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 0;
margin-bottom: 0.5em;
font-weight: 500;
}
p {
margin-top: 0;
margin-bottom: 1em;
}
abbr[title],
abbr[data-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline;
text-decoration: underline dotted;
border-bottom: 0;
cursor: help;
}
address {
margin-bottom: 1em;
font-style: normal;
line-height: inherit;
}
input[type='text'],
input[type='password'],
input[type='number'],
textarea {
-webkit-appearance: none;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1em;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 500;
}
dd {
margin-bottom: 0.5em;
margin-left: 0;
}
blockquote {
margin: 0 0 1em;
}
dfn {
font-style: italic;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
pre,
code,
kbd,
samp {
font-size: 1em;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
}
pre {
margin-top: 0;
margin-bottom: 1em;
overflow: auto;
}
figure {
margin: 0 0 1em;
}
img {
vertical-align: middle;
border-style: none;
}
a,
area,
button,
[role='button'],
input:not([type='range']),
label,
select,
summary,
textarea {
touch-action: manipulation;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75em;
padding-bottom: 0.3em;
text-align: left;
caption-side: bottom;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
color: inherit;
font-size: inherit;
font-family: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
button,
html [type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
}
button::-moz-focus-inner,
[type='button']::-moz-focus-inner,
[type='reset']::-moz-focus-inner,
[type='submit']::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type='radio'],
input[type='checkbox'] {
box-sizing: border-box;
padding: 0;
}
input[type='date'],
input[type='time'],
input[type='datetime-local'],
input[type='month'] {
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
margin: 0;
padding: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
margin-bottom: 0.5em;
padding: 0;
color: inherit;
font-size: 1.5em;
line-height: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type='number']::-webkit-inner-spin-button,
[type='number']::-webkit-outer-spin-button {
height: auto;
}
[type='search'] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type='search']::-webkit-search-cancel-button,
[type='search']::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
mark {
padding: 0.2em;
background-color: #feffe6;
}

View File

@ -0,0 +1,57 @@
/* eslint-disable import/prefer-default-export */
import type { CSSObject } from '@antd-tiny-vue/cssinjs'
export const roundedArrow = (width: number, innerRadius: number, outerRadius: number, bgColor: string, boxShadow: string): CSSObject => {
const unitWidth = width / 2
const ax = 0
const ay = unitWidth
const bx = (outerRadius * 1) / Math.sqrt(2)
const by = unitWidth - outerRadius * (1 - 1 / Math.sqrt(2))
const cx = unitWidth - innerRadius * (1 / Math.sqrt(2))
const cy = outerRadius * (Math.sqrt(2) - 1) + innerRadius * (1 / Math.sqrt(2))
const dx = 2 * unitWidth - cx
const dy = cy
const ex = 2 * unitWidth - bx
const ey = by
const fx = 2 * unitWidth - ax
const fy = ay
const shadowWidth = unitWidth * Math.sqrt(2) + outerRadius * (Math.sqrt(2) - 2)
return {
pointerEvents: 'none',
width,
height: width,
overflow: 'hidden',
'&::before': {
position: 'absolute',
bottom: 0,
insetInlineStart: 0,
width,
height: width / 2,
background: bgColor,
clipPath: `path('M ${ax} ${ay} A ${outerRadius} ${outerRadius} 0 0 0 ${bx} ${by} L ${cx} ${cy} A ${innerRadius} ${innerRadius} 0 0 1 ${dx} ${dy} L ${ex} ${ey} A ${outerRadius} ${outerRadius} 0 0 0 ${fx} ${fy} Z')`,
content: '""'
},
'&::after': {
content: '""',
position: 'absolute',
width: shadowWidth,
height: shadowWidth,
bottom: 0,
insetInline: 0,
margin: 'auto',
borderRadius: {
_skip_check_: true,
value: `0 0 ${innerRadius}px 0`
},
transform: 'translateY(50%) rotate(-135deg)',
boxShadow,
zIndex: 0,
background: 'transparent'
}
}
}

31
components/theme/index.ts Normal file
View File

@ -0,0 +1,31 @@
/* eslint-disable import/prefer-default-export */
import { defaultConfig, useToken as useInternalToken } from './internal'
import type { GlobalToken } from './interface'
import defaultAlgorithm from './themes/default'
import darkAlgorithm from './themes/dark'
import compactAlgorithm from './themes/compact'
// ZombieJ: We export as object to user but array in internal.
// This is used to minimize the bundle size for antd package but safe to refactor as object also.
// Please do not export internal `useToken` directly to avoid something export unexpected.
/** Get current context Design Token. Will be different if you are using nest theme config. */
function useToken() {
const [theme, token, hashId] = useInternalToken()
return { theme, token, hashId }
}
export { type GlobalToken }
export default {
/** @private Test Usage. Do not use in production. */
defaultConfig,
/** Default seedToken */
defaultSeed: defaultConfig.token,
useToken,
defaultAlgorithm,
darkAlgorithm,
compactAlgorithm
}

View File

@ -0,0 +1,149 @@
import type { CSSProperties } from 'vue'
import type { MapToken } from './maps'
// ======================================================================
// == Alias Token ==
// ======================================================================
// 🔥🔥🔥🔥🔥🔥🔥 DO NOT MODIFY THIS. PLEASE CONTACT DESIGNER. 🔥🔥🔥🔥🔥🔥🔥
export interface AliasToken extends MapToken {
// Background
colorFillContentHover: string
colorFillAlter: string
colorFillContent: string
colorBgContainerDisabled: string
colorBgTextHover: string
colorBgTextActive: string
// Border
colorBorderBg: string
/**
* @nameZH 分割线颜色
* @desc 用于作为分割线的颜色,此颜色和 colorBorderSecondary 的颜色一致,但是用的是透明色。
*/
colorSplit: string
// Text
colorTextPlaceholder: string
colorTextDisabled: string
colorTextHeading: string
colorTextLabel: string
colorTextDescription: string
colorTextLightSolid: string
/** Weak action. Such as `allowClear` or Alert close button */
colorIcon: string
/** Weak action hover color. Such as `allowClear` or Alert close button */
colorIconHover: string
colorLink: string
colorLinkHover: string
colorLinkActive: string
colorHighlight: string
controlOutline: string
colorWarningOutline: string
colorErrorOutline: string
// Font
/** Operation icon in Select, Cascader, etc. icon fontSize. Normal is same as fontSizeSM */
fontSizeIcon: number
/** For heading like h1, h2, h3 or option selected item */
fontWeightStrong: number
// Control
controlOutlineWidth: number
controlItemBgHover: string // Note. It also is a color
controlItemBgActive: string // Note. It also is a color
controlItemBgActiveHover: string // Note. It also is a color
controlInteractiveSize: number
controlItemBgActiveDisabled: string // Note. It also is a color
// Padding
paddingXXS: number
paddingXS: number
paddingSM: number
padding: number
paddingMD: number
paddingLG: number
paddingXL: number
// Padding Content
paddingContentHorizontalLG: number
paddingContentHorizontal: number
paddingContentHorizontalSM: number
paddingContentVerticalLG: number
paddingContentVertical: number
paddingContentVerticalSM: number
// Margin
marginXXS: number
marginXS: number
marginSM: number
margin: number
marginMD: number
marginLG: number
marginXL: number
marginXXL: number
// =============== Legacy: should be remove ===============
opacityLoading: number
boxShadow: string
boxShadowSecondary: string
boxShadowTertiary: string
linkDecoration: CSSProperties['textDecoration']
linkHoverDecoration: CSSProperties['textDecoration']
linkFocusDecoration: CSSProperties['textDecoration']
controlPaddingHorizontal: number
controlPaddingHorizontalSM: number
// Media queries breakpoints
screenXS: number
screenXSMin: number
screenXSMax: number
screenSM: number
screenSMMin: number
screenSMMax: number
screenMD: number
screenMDMin: number
screenMDMax: number
screenLG: number
screenLGMin: number
screenLGMax: number
screenXL: number
screenXLMin: number
screenXLMax: number
screenXXL: number
screenXXLMin: number
/** Used for DefaultButton, Switch which has default outline */
controlTmpOutline: string
// FIXME: component box-shadow, should be removed
/** @internal */
boxShadowPopoverArrow: string
/** @internal */
boxShadowCard: string
/** @internal */
boxShadowDrawerRight: string
/** @internal */
boxShadowDrawerLeft: string
/** @internal */
boxShadowDrawerUp: string
/** @internal */
boxShadowDrawerDown: string
/** @internal */
boxShadowTabsOverflowLeft: string
/** @internal */
boxShadowTabsOverflowRight: string
/** @internal */
boxShadowTabsOverflowTop: string
/** @internal */
boxShadowTabsOverflowBottom: string
}

View File

@ -0,0 +1,119 @@
// import type { ComponentToken as AlertComponentToken } from '../../alert/style'
// import type { ComponentToken as AnchorComponentToken } from '../../anchor/style'
// import type { ComponentToken as AvatarComponentToken } from '../../avatar/style'
// import type { ComponentToken as BackTopComponentToken } from '../../back-top/style'
import type { ComponentToken as ButtonComponentToken } from '../../button/style'
// import type { ComponentToken as FloatButtonComponentToken } from '../../floats-button/style'
// import type { ComponentToken as CalendarComponentToken } from '../../calendar/style'
// import type { ComponentToken as CardComponentToken } from '../../card/style'
// import type { ComponentToken as CarouselComponentToken } from '../../carousel/style'
// import type { ComponentToken as CascaderComponentToken } from '../../cascader/style'
// import type { ComponentToken as CheckboxComponentToken } from '../../checkbox/style'
// import type { ComponentToken as CollapseComponentToken } from '../../collapse/style'
// import type { ComponentToken as DatePickerComponentToken } from '../../date-picker/style'
// import type { ComponentToken as DividerComponentToken } from '../../divider/style'
// import type { ComponentToken as DropdownComponentToken } from '../../dropdown/style'
// import type { ComponentToken as DrawerComponentToken } from '../../drawer/style'
// import type { ComponentToken as EmptyComponentToken } from '../../empty/style'
// import type { ComponentToken as ImageComponentToken } from '../../image/style'
// import type { ComponentToken as InputNumberComponentToken } from '../../input-number/style'
// import type { ComponentToken as LayoutComponentToken } from '../../layout/style'
// import type { ComponentToken as ListComponentToken } from '../../list/style'
// import type { ComponentToken as MentionsComponentToken } from '../../mentions/style'
// import type { ComponentToken as MenuComponentToken } from '../../menu/style'
// import type { ComponentToken as MessageComponentToken } from '../../message/style'
// import type { ComponentToken as ModalComponentToken } from '../../modal/style'
// import type { ComponentToken as NotificationComponentToken } from '../../notification/style'
// import type { ComponentToken as PopconfirmComponentToken } from '../../popconfirm/style'
// import type { ComponentToken as PopoverComponentToken } from '../../popover/style'
// import type { ComponentToken as ProgressComponentToken } from '../../progress/style'
// import type { ComponentToken as RadioComponentToken } from '../../radio/style'
// import type { ComponentToken as RateComponentToken } from '../../rate/style'
// import type { ComponentToken as ResultComponentToken } from '../../result/style'
// import type { ComponentToken as SegmentedComponentToken } from '../../segmented/style'
// import type { ComponentToken as SelectComponentToken } from '../../select/style'
// import type { ComponentToken as SkeletonComponentToken } from '../../skeleton/style'
// import type { ComponentToken as SliderComponentToken } from '../../slider/style'
import type { ComponentToken as SpaceComponentToken } from '../../space/style'
// import type { ComponentToken as SpinComponentToken } from '../../spin/style'
// import type { ComponentToken as StepsComponentToken } from '../../steps/style'
// import type { ComponentToken as TableComponentToken } from '../../table/style'
// import type { ComponentToken as TabsComponentToken } from '../../tabs/style'
// import type { ComponentToken as TagComponentToken } from '../../tag/style'
// import type { ComponentToken as TimelineComponentToken } from '../../timeline/style'
// import type { ComponentToken as TooltipComponentToken } from '../../tooltip/style'
// import type { ComponentToken as TransferComponentToken } from '../../transfer/style'
// import type { ComponentToken as TypographyComponentToken } from '../../typography/style'
// import type { ComponentToken as UploadComponentToken } from '../../upload/style'
// import type { ComponentToken as TourComponentToken } from '../../tour/style'
// import type { ComponentToken as QRCodeComponentToken } from '../../qrcode/style'
// import type { ComponentToken as AppComponentToken } from '../../app/style'
import type { ComponentToken as WaveToken } from '../../_util/wave/style'
export interface ComponentTokenMap {
Affix?: {}
// Alert?: AlertComponentToken
// Anchor?: AnchorComponentToken
// Avatar?: AvatarComponentToken
// BackTop?: BackTopComponentToken
// Badge?: {}
Button?: ButtonComponentToken
// Breadcrumb?: {}
// Card?: CardComponentToken
// Carousel?: CarouselComponentToken
// Cascader?: CascaderComponentToken
// Checkbox?: CheckboxComponentToken
// Collapse?: CollapseComponentToken
// DatePicker?: DatePickerComponentToken
// Descriptions?: {}
// Divider?: DividerComponentToken
// Drawer?: DrawerComponentToken
// Dropdown?: DropdownComponentToken
// Empty?: EmptyComponentToken
// FloatButton?: FloatButtonComponentToken
// Form?: {}
// Grid?: {}
// Image?: ImageComponentToken
// Input?: {}
// InputNumber?: InputNumberComponentToken
// Layout?: LayoutComponentToken
// List?: ListComponentToken
// Mentions?: MentionsComponentToken
// Notification?: NotificationComponentToken
// Pagination?: {}
// Popover?: PopoverComponentToken
// Popconfirm?: PopconfirmComponentToken
// Rate?: RateComponentToken
// Radio?: RadioComponentToken
// Result?: ResultComponentToken
// Segmented?: SegmentedComponentToken
// Select?: SelectComponentToken
// Skeleton?: SkeletonComponentToken
// Slider?: SliderComponentToken
// Spin?: SpinComponentToken
// Statistic?: {}
// Switch?: {}
// Tag?: TagComponentToken
// Tree?: {}
// TreeSelect?: {}
// Typography?: TypographyComponentToken
// Timeline?: TimelineComponentToken
// Transfer?: TransferComponentToken
// Tabs?: TabsComponentToken
// Calendar?: CalendarComponentToken
// Steps?: StepsComponentToken
// Menu?: MenuComponentToken
// Modal?: ModalComponentToken
// Message?: MessageComponentToken
// Upload?: UploadComponentToken
// Tooltip?: TooltipComponentToken
// Table?: TableComponentToken
Space?: SpaceComponentToken
// Progress?: ProgressComponentToken
// Tour?: TourComponentToken
// QRCode?: QRCodeComponentToken
// App?: AppComponentToken
//
// /** @private Internal TS definition. Do not use. */
Wave?: WaveToken
}

View File

@ -0,0 +1,16 @@
import type { ComponentTokenMap } from './components'
import type { AliasToken } from './alias'
export type OverrideToken = {
[key in keyof ComponentTokenMap]: Partial<ComponentTokenMap[key]> & Partial<AliasToken>
}
/** Final token which contains the components level override */
export type GlobalToken = AliasToken & ComponentTokenMap
export { PresetColors } from './presetColors'
export type { PresetColorType, ColorPalettes, PresetColorKey } from './presetColors'
export type { SeedToken } from './seeds'
export type { MapToken, ColorMapToken, ColorNeutralMapToken, CommonMapToken, HeightMapToken, SizeMapToken, FontMapToken, StyleMapToken } from './maps'
export type { AliasToken } from './alias'
export type { ComponentTokenMap } from './components'

View File

@ -0,0 +1,431 @@
export interface ColorNeutralMapToken {
/**
* @internal
*/
colorTextBase: string
/**
* @internal
*/
colorBgBase: string
// ---------- Text ---------- //
/**
* @nameZH 一级文本色
* @desc 最深的文本色。为了符合W3C标准默认的文本颜色使用了该色同时这个颜色也是最深的中性色。
*/
colorText: string
/**
* @nameZH 二级文本色
* @desc 作为第二梯度的文本色,一般用在不那么需要强化文本颜色的场景,例如 Label 文本、Menu 的文本选中态等场景。
*/
colorTextSecondary: string
/**
* @nameZH 三级文本色
* @desc 第三级文本色一般用于描述性文本,例如表单的中的补充说明文本、列表的描述性文本等场景。
*/
colorTextTertiary: string
/**
* @nameZH 四级文本色
* @desc 第四级文本色是最浅的文本色,例如表单的输入提示文本、禁用色文本等。
*/
colorTextQuaternary: string
// ---------- Border ---------- //
/**
* @nameZH 一级边框色
* @nameEN Default Border Color
* @desc 默认使用的边框颜色, 用于分割不同的元素,例如:表单的分割线、卡片的分割线等。
* @descEN Default border color, used to separate different elements, such as: form separator, card separator, etc.
*/
colorBorder: string
/**
* @nameZH 二级边框色
* @nameEN Secondary Border Color
* @desc 比默认使用的边框色要浅一级,此颜色和 colorSplit 的颜色一致。使用的是实色。
* @descEN Slightly lighter than the default border color, this color is the same as `colorSplit`. Solid color is used.
*/
colorBorderSecondary: string
// ---------- Fill ---------- //
/**
* @nameZH 一级填充色
* @desc 最深的填充色,用于拉开与二、三级填充色的区分度,目前只用在 Slider 的 hover 效果。
*/
colorFill: string
/**
* @nameZH 二级填充色
* @desc 二级填充色可以较为明显地勾勒出元素形体,如 Rate、Skeleton 等。也可以作为三级填充色的 Hover 状态,如 Table 等。
*/
colorFillSecondary: string
/**
* @nameZH 三级填充色
* @desc 三级填充色用于勾勒出元素形体的场景,如 Slider、Segmented 等。如无强调需求的情况下,建议使用三级填色作为默认填色。
*/
colorFillTertiary: string
/**
* @nameZH 四级填充色
* @desc 最弱一级的填充色,适用于不易引起注意的色块,例如斑马纹、区分边界的色块等。
*/
colorFillQuaternary: string
// ---------- Surface ---------- //
/**
* @nameZH 布局背景色
* @desc 该色用于页面整体布局的背景色,只有需要在页面中处于 B1 的视觉层级时才会使用该 token其他用法都是错误的
*/
colorBgLayout: string
/**
* @nameZH 组件容器背景色
* @desc 组件的容器背景色,例如:默认按钮、输入框等。务必不要将其与 `colorBgElevated` 混淆。
*/
colorBgContainer: string
/**
* @nameZH 浮层容器背景色
* @desc 浮层容器背景色,在暗色模式下该 token 的色值会比 `colorBgContainer` 要亮一些。例如:模态框、弹出框、菜单等。
*/
colorBgElevated: string
/**
* @nameZH 引起注意的背景色
* @desc 该色用于引起用户强烈关注注意的背景色,目前只用在 Tooltip 的背景色上。
*/
colorBgSpotlight: string
}
/**
* 品牌色梯度变量
*/
interface ColorPrimaryMapToken {
/**
* @nameZH 品牌主色
* @desc 品牌色是体现产品特性和传播理念最直观的视觉元素之一,用于产品的主色调、主按钮、主图标、主文本等 */
colorPrimary: string // 6
/**
* @nameZH 主色浅色背景色
* @nameEN Light Background Color of Primary Color
* @desc 主色浅色背景颜色,一般用于视觉层级较弱的选中状态。
* @descEN Light background color of primary color, usually used for weak visual level selection state.
*/
colorPrimaryBg: string // 1
/**
* @nameZH 主色浅色背景悬浮态
* @desc 与主色浅色背景颜色相对应的悬浮态颜色。
*/
colorPrimaryBgHover: string // 2
/**
* @nameZH 主色描边色
* @desc 主色梯度下的描边用色,用在 Slider 组件的描边上
*/
colorPrimaryBorder: string // 3
/**
* @nameZH 主色描边色悬浮态
* @desc 主色梯度下的描边用色的悬浮态Slider 、Button 等组件的描边 Hover 时会使用
*/
colorPrimaryBorderHover: string // 4
/**
* @nameZH 主色悬浮态
* @desc 主色梯度下的悬浮态,使用频率很高
*/
colorPrimaryHover: string // 5
/**
* @nameZH 主色激活态
* @desc 主色梯度下的深色激活态
*/
colorPrimaryActive: string // 7
/**
* @nameZH 主色文本悬浮态
* @desc 主色梯度下的文本悬浮态
*/
colorPrimaryTextHover: string // 8
/**
* @nameZH 主色文本
* @desc 主色梯度下的文本颜色
*/
colorPrimaryText: string // 9
/**
* @nameZH 主色文本
* @desc 主色梯度下的文本激活态
*/
colorPrimaryTextActive: string // 10
}
interface ColorSuccessMapToken {
/**
* @nameZH 成功色的浅色背景颜色
* @nameEN Light Background Color of Success Color
* @desc 成功色的浅色背景颜色,用于 Tag 和 Alert 的成功态背景色
* @descEN Light background color of success color, used for Tag and Alert success state background color
*/
colorSuccessBg: string // 1
/**
* @nameZH 成功色的浅色背景色悬浮态
* @nameEN Hover State Color of Light Success Background
* @desc 成功色浅色背景颜色,一般用于视觉层级较弱的选中状态,不过 antd 目前没有使用到该 token
* @descEN Light background color of success color, but antd does not use this token currently
*/
colorSuccessBgHover: string // 2
/**
* @nameZH 成功色的描边色
* @desc 成功色的描边色,用于 Tag 和 Alert 的成功态描边色
*/
colorSuccessBorder: string // 3
/**
* @nameZH 成功色的描边色悬浮态
* @desc 成功色的描边色悬浮态
*/
colorSuccessBorderHover: string // 4
/**
* @nameZH 成功色的深色悬浮态
* @desc 成功色的深色悬浮态
*/
colorSuccessHover: string // 5
/**
* @nameZH 成功色
* @desc 默认的成功色,如 Result、Progress 等组件中都有使用该颜色
*/
colorSuccess: string // 6
/**
* @nameZH 成功色的深色激活态
* @desc 成功色的深色激活态
*/
colorSuccessActive: string // 7
/**
* @nameZH 成功色的文本悬浮态
* @desc 成功色的文本悬浮态
*/
colorSuccessTextHover: string // 8
/**
* @nameZH 成功色的文本默认态
* @desc 成功色的文本默认态
*/
colorSuccessText: string // 9
/**
* @nameZH 成功色的文本激活态
* @desc 成功色的文本激活态
*/
colorSuccessTextActive: string // 10
}
interface ColorWarningMapToken {
/**
* @nameZH 警戒色的浅色背景颜色
*/
colorWarningBg: string // 1
/**
* @nameZH 警戒色的浅色背景色悬浮态
* @desc 警戒色的浅色背景色悬浮态
*/
colorWarningBgHover: string // 2
/**
* @nameZH 警戒色的描边色
* @desc 警戒色的描边色
*/
colorWarningBorder: string // 3
/**
* @nameZH 警戒色的描边色悬浮态
* @desc 警戒色的描边色悬浮态
*/
colorWarningBorderHover: string // 4
/**
* @nameZH 警戒色的深色悬浮态
* @desc 警戒色的深色悬浮态
*/
colorWarningHover: string // 5
/**
* @nameZH 警戒色
* @desc 最常用的警戒色,例如 Notification、 Alert等警告类组件或 Input 输入类等组件会使用该颜色
*/
colorWarning: string // 6
/**
* @nameZH 警戒色的深色激活态
* @desc 警戒色的深色激活态
*/
colorWarningActive: string // 7
/**
* @nameZH 警戒色的文本悬浮态
* @desc 警戒色的文本悬浮态
*/
colorWarningTextHover: string // 8
/**
* @nameZH 警戒色的文本默认态
* @desc 警戒色的文本默认态
*/
colorWarningText: string // 9
/**
* @nameZH 警戒色的文本激活态
* @desc 警戒色的文本激活态
*/
colorWarningTextActive: string // 10
}
interface ColorInfoMapToken {
/**
* @nameZH 信息色的浅色背景颜色
* @desc 信息色的浅色背景颜色
*/
colorInfoBg: string // 1
/**
* @nameZH 信息色的浅色背景色悬浮态
* @desc 信息色的浅色背景色悬浮态
*/
colorInfoBgHover: string // 2
/**
* @nameZH 信息色的描边色
*/
colorInfoBorder: string // 3
/**
* @nameZH 信息色的描边色悬浮态
*/
colorInfoBorderHover: string // 4
/**
* @nameZH 信息色的深色悬浮态
*/
colorInfoHover: string // 5
/**
* @nameZH 信息色
*/
colorInfo: string // 6
/**
* @nameZH 信息色的深色激活态
*/
colorInfoActive: string // 7
/**
* @nameZH 信息色的文本悬浮态
*/
colorInfoTextHover: string // 8
/**
* @nameZH 信息色的文本默认态
*/
colorInfoText: string // 9
/**
* @nameZH 信息色的文本激活态
*/
colorInfoTextActive: string // 10
}
interface ColorErrorMapToken {
/**
* @nameZH 错误色的浅色背景颜色
*/
colorErrorBg: string // 1
/**
* @nameZH 错误色的浅色背景色悬浮态
*/
colorErrorBgHover: string // 2
/**
* @nameZH 错误色的描边色
*/
colorErrorBorder: string // 3
/**
* @nameZH 错误色的描边色悬浮态
*/
colorErrorBorderHover: string // 4
/**
* @nameZH 错误色的深色悬浮态
*/
colorErrorHover: string // 5
/**
* @nameZH 错误色
*/
colorError: string // 6
/**
* @nameZH 错误色的深色激活态
*/
colorErrorActive: string // 7
/**
* @nameZH 错误色的文本悬浮态
*/
colorErrorTextHover: string // 8
/**
* @nameZH 错误色的文本默认态
*/
colorErrorText: string // 9
/**
* @nameZH 错误色的文本激活态
*/
colorErrorTextActive: string // 10
}
export interface ColorMapToken extends ColorNeutralMapToken, ColorPrimaryMapToken, ColorSuccessMapToken, ColorWarningMapToken, ColorErrorMapToken, ColorInfoMapToken {
/**
* @nameZH 纯白色
* @desc 不随主题变化的纯白色
* @descEN Pure white color don't changed by theme
* @default #FFFFFF
*/
colorWhite: string
/**
* @nameZH 浮层的背景蒙层颜色
* @nameEN Background color of the mask
* @desc 浮层的背景蒙层颜色用于遮罩浮层下面的内容Modal、Drawer 等组件的蒙层使用的是该 token
* @descEN The background color of the mask, used to cover the content below the mask, Modal, Drawer and other components use this token
*/
colorBgMask: string
/**
* @nameZH 纯黑色
* @desc 不随主题变化的纯黑色
* @default #0000
*/
// colorBlack: string;
}

View File

@ -0,0 +1,49 @@
export interface FontMapToken {
// Font Size
fontSizeSM: number
fontSize: number
fontSizeLG: number
fontSizeXL: number
/**
* @nameZH 一级标题字号
* @desc H1 标签所使用的字号
* @default 38
*/
fontSizeHeading1: number
/**
* @nameZH 二级标题字号
* @desc h2 标签所使用的字号
* @default 30
*/
fontSizeHeading2: number
/**
* @nameZH 三级标题字号
* @desc h3 标签使用的字号
* @default 24
*/
fontSizeHeading3: number
/**
* @nameZH 四级标题字号
* @desc h4 标签使用的字号
* @default 20
*/
fontSizeHeading4: number
/**
* @nameZH 五级标题字号
* @desc h5 标签使用的字号
* @default 16
*/
fontSizeHeading5: number
// LineHeight
lineHeight: number
lineHeightLG: number
lineHeightSM: number
lineHeightHeading1: number
lineHeightHeading2: number
lineHeightHeading3: number
lineHeightHeading4: number
lineHeightHeading5: number
}

View File

@ -0,0 +1,25 @@
import type { ColorPalettes } from '../presetColors'
import type { SeedToken } from '../seeds'
import type { HeightMapToken, SizeMapToken } from './size'
import type { ColorMapToken } from './colors'
import type { StyleMapToken } from './style'
import type { FontMapToken } from './font'
export * from './colors'
export * from './style'
export * from './size'
export * from './font'
export interface CommonMapToken extends StyleMapToken {
// Motion
motionDurationFast: string
motionDurationMid: string
motionDurationSlow: string
}
// ======================================================================
// == Map Token ==
// ======================================================================
// 🔥🔥🔥🔥🔥🔥🔥 DO NOT MODIFY THIS. PLEASE CONTACT DESIGNER. 🔥🔥🔥🔥🔥🔥🔥
export interface MapToken extends SeedToken, ColorPalettes, ColorMapToken, SizeMapToken, HeightMapToken, StyleMapToken, FontMapToken, CommonMapToken {}

View File

@ -0,0 +1,68 @@
export interface SizeMapToken {
/**
* @nameZH XXL
* @default 48
*/
sizeXXL: number
/**
* @nameZH XL
* @default 32
*/
sizeXL: number
/**
* @nameZH LG
* @default 24
*/
sizeLG: number
/**
* @nameZH MD
* @default 20
*/
sizeMD: number
/** Same as size by default, but could be larger in compact mode */
sizeMS: number
/**
* @nameZH 默认
* @desc 默认尺寸
* @default 16
*/
size: number
/**
* @nameZH SM
* @default 12
*/
sizeSM: number
/**
* @nameZH XS
* @default 8
*/
sizeXS: number
/**
* @nameZH XXS
* @default 4
*/
sizeXXS: number
}
export interface HeightMapToken {
// Control
/** Only Used for control inside component like Multiple Select inner selection item */
/**
* @nameZH 更小的组件高度
* @nameEN XS component height
*/
controlHeightXS: number
/**
* @nameZH 较小的组件高度
* @nameEN SM component height
*/
controlHeightSM: number
/**
* @nameZH 较高的组件高度
* @nameEN LG component height
*/
controlHeightLG: number
}

View File

@ -0,0 +1,38 @@
export interface StyleMapToken {
/**
* @nameZH 线宽
* @nameEN Line Width
* @desc 描边类组件的默认线宽,如 Button、Input、Select 等输入类控件。
* @descEN The default line width of the outline class components, such as Button, Input, Select, etc.
* @default 1
*/
lineWidthBold: number
/**
* @nameZH XS号圆角
* @desc XS号圆角用于组件中的一些小圆角如 Segmented 、Arrow 等一些内部圆角的组件样式中。
* @descEN XS size border radius, used in some small border radius components, such as Segmented, Arrow and other components.
* @default 2
*/
borderRadiusXS: number
/**
* @nameZH SM号圆角
* @nameEN SM Border Radius
* @desc SM号圆角用于组件小尺寸下的圆角如 Button、Input、Select 等输入类控件在 small size 下的圆角
* @descEN SM size border radius, used in small size components, such as Button, Input, Select and other input components in small size
* @default 4
*/
borderRadiusSM: number
/**
* @nameZH LG号圆角
* @nameEN LG Border Radius
* @desc LG号圆角用于组件中的一些大圆角如 Card、Modal 等一些组件样式。
* @descEN LG size border radius, used in some large border radius components, such as Card, Modal and other components.
* @default 8
*/
borderRadiusLG: number
/**
* @default 4
*/
borderRadiusOuter: number
}

View File

@ -0,0 +1,11 @@
export const PresetColors = ['blue', 'purple', 'cyan', 'green', 'magenta', 'pink', 'red', 'orange', 'yellow', 'volcano', 'geekblue', 'lime', 'gold'] as const
export type PresetColorKey = (typeof PresetColors)[number]
export type PresetColorType = Record<PresetColorKey, string>
type ColorPaletteKeyIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
export type ColorPalettes = {
[key in `${keyof PresetColorType}-${ColorPaletteKeyIndex}`]: string
}

View File

@ -0,0 +1,223 @@
import type { PresetColorType } from './presetColors'
// ======================================================================
// == Seed Token ==
// ======================================================================
// 🔥🔥🔥🔥🔥🔥🔥 DO NOT MODIFY THIS. PLEASE CONTACT DESIGNER. 🔥🔥🔥🔥🔥🔥🔥
export interface SeedToken extends PresetColorType {
// ---------- Color ---------- //
/**
* @nameZH 品牌主色
* @nameEN Brand Color
* @desc 品牌色是体现产品特性和传播理念最直观的视觉元素之一。在你完成品牌主色的选取之后,我们会自动帮你生成一套完整的色板,并赋予它们有效的设计语义
* @descEN Brand color is one of the most direct visual elements to reflect the characteristics and communication of the product. After you have selected the brand color, we will automatically generate a complete color palette and assign it effective design semantics.
*/
colorPrimary: string
/**
* @nameZH 成功色
* @nameEN Success Color
* @desc 用于表示操作成功的 Token 序列,如 Result、Progress 等组件会使用该组梯度变量。
* @descEN Used to represent the token sequence of operation success, such as Result, Progress and other components will use these map tokens.
*/
colorSuccess: string
/**
* @nameZH 警戒色
* @nameEN Warning Color
* @desc 用于表示操作警告的 Token 序列,如 Notification、 Alert等警告类组件或 Input 输入类等组件会使用该组梯度变量。
* @descEN Used to represent the warning map token, such as Notification, Alert, etc. Alert or Control component(like Input) will use these map tokens.
*/
colorWarning: string
/**
* @nameZH 错误色
* @nameEN Error Color
* @desc 用于表示操作失败的 Token 序列如失败按钮、错误状态提示Result组件等。
* @descEN Used to represent the visual elements of the operation failure, such as the error Button, error Result component, etc.
*/
colorError: string
/**
* @nameZH 信息色
* @nameEN Info Color
* @desc 用于表示操作信息的 Token 序列,如 Alert 、Tag、 Progress 等组件都有用到该组梯度变量。
* @descEN Used to represent the operation information of the Token sequence, such as Alert, Tag, Progress, and other components use these map tokens.
*/
colorInfo: string
/**
* @nameZH 基础文本色
* @nameEN Seed Text Color
* @desc 用于派生文本色梯度的基础变量v5 中我们添加了一层文本色的派生算法可以产出梯度明确的文本色的梯度变量。但请不要在代码中直接使用该 Seed Token
* @descEN Used to derive the base variable of the text color gradient. In v5, we added a layer of text color derivation algorithm to produce gradient variables of text color gradient. But please do not use this Seed Token directly in the code!
*/
colorTextBase: string
/**
* @nameZH 基础背景色
* @nameEN Seed Background Color
* @desc 用于派生背景色梯度的基础变量v5 中我们添加了一层背景色的派生算法可以产出梯度明确的背景色的梯度变量。但请不要在代码中直接使用该 Seed Token
* @descEN Used to derive the base variable of the background color gradient. In v5, we added a layer of background color derivation algorithm to produce map token of background color. But PLEASE DO NOT USE this Seed Token directly in the code!
*/
colorBgBase: string
// ---------- Font ---------- //
/**
* @nameZH 字体
* @nameEN Font family for default text
* @desc Ant Design 的字体家族中优先使用系统默认的界面字体,同时提供了一套利于屏显的备用字体库,来维护在不同平台以及浏览器的显示下,字体始终保持良好的易读性和可读性,体现了友好、稳定和专业的特性。
*/
fontFamily: string
/**
* @nameZH 代码字体
* @nameEN Font family for code text
* @desc 代码字体,用于 Typography 内的 code、pre 和 kbd 类型的元素
*/
fontFamilyCode: string
/**
* @nameZH 默认字号
* @nameEN Default Font Size
* @desc 设计系统中使用最广泛的字体大小,文本梯度也将基于该字号进行派生。
* @default 14
*/
fontSize: number
// ---------- Line ---------- //
/**
* @nameZH 基础线宽
* @nameEN Base Line Width
* @desc 用于控制组件边框、分割线等的宽度
* @descEN Border width of base components
*/
lineWidth: number
/**
* @nameZH 线条样式
* @nameEN Line Style
* @desc 用于控制组件边框、分割线等的样式,默认是实线
* @descEN Border style of base components
*/
lineType: string
// ---------- BorderRadius ---------- //
/**
* @nameZH 基础圆角
* @nameEN Base Border Radius
* @descEN Border radius of base components
* @desc 基础组件的圆角大小,例如按钮、输入框、卡片等
*/
borderRadius: number
// ---------- Size ---------- //
/**
* @nameZH 尺寸变化单位
* @nameEN Size Change Unit
* @desc 用于控制组件尺寸的变化单位,在 Ant Design 中我们的基础单位为 4 ,便于更加细致地控制尺寸梯度
* @descEN The unit of size change, in Ant Design, our base unit is 4, which is more fine-grained control of the size step
* @default 4
*/
sizeUnit: number
/**
* @nameZH 尺寸步长
* @nameEN Size Base Step
* @desc 用于控制组件尺寸的基础步长,尺寸步长结合尺寸变化单位,就可以派生各种尺寸梯度。通过调整步长即可得到不同的布局模式,例如 V5 紧凑模式下的尺寸步长为 2
* @descEN The base step of size change, the size step combined with the size change unit, can derive various size steps. By adjusting the step, you can get different layout modes, such as the size step of the compact mode of V5 is 2
* @default 4
*/
sizeStep: number
/**
* @nameZH 组件箭头尺寸
*/
sizePopupArrow: number
/**
* @nameZH 基础高度
* @nameEN Base Control Height
* @desc Ant Design 中按钮和输入框等基础控件的高度
* @descEN The height of the basic controls such as buttons and input boxes in Ant Design
* @default 32
*/
controlHeight: number
// ---------- zIndex ---------- //
/**
* @nameZH 基础 zIndex
* @nameEN Base zIndex
* @desc 所有组件的基础 Z 轴值,用于一些悬浮类的组件的可以基于该值 Z 轴控制层级,例如 BackTop、 Affix 等
* @descEN The base Z axis value of all components, which can be used to control the level of some floating components based on the Z axis value, such as BackTop, Affix, etc.
*
* @default 0
*/
zIndexBase: number
/**
* @nameZH 浮层基础 zIndex
* @nameEN popup base zIndex
* @desc 浮层类组件的基础 Z 轴值,用于一些悬浮类的组件的可以基于该值 Z 轴控制层级,例如 FloatButton、 Affix、Modal 等
* @descEN Base zIndex of component like FloatButton, Affix which can be cover by large popup
* @default 1000
*/
zIndexPopupBase: number
// ---------- Opacity ---------- //
/**
* @nameZH 图片不透明度
* @nameEN Define default Image opacity. Useful when in dark-like theme
*/
opacityImage: number
// ---------- motion ---------- //
// TODO: 缺一个懂 motion 的人来收敛 Motion 相关的 Token
/**
* @nameZH 动画时长变化单位
* @nameEN Animation Duration Unit
* @desc 用于控制动画时长的变化单位
* @descEN The unit of animation duration change
* @default 100ms
*/
motionUnit: number
/**
* @nameZH 动画基础时长
*/
motionBase: number
motionEaseOutCirc: string
motionEaseInOutCirc: string
motionEaseInOut: string
motionEaseOutBack: string
motionEaseInBack: string
motionEaseInQuint: string
motionEaseOutQuint: string
motionEaseOut: string
// ---------- Style ---------- //
/**
* @nameZH 线框风格
* @nameEN Wireframe Style
* @desc 用于将组件的视觉效果变为线框化,如果需要使用 V4 的效果,需要开启配置项
* @default false
*/
wireframe: boolean
}

View File

@ -0,0 +1,97 @@
import type { CSSInterpolation, Theme } from '@antd-tiny-vue/cssinjs'
import { createTheme, useCacheToken, useStyleRegister } from '@antd-tiny-vue/cssinjs'
import { createInjectionState, objectType, someType } from '@v-c/utils'
import type { ComputedRef, VNodeChild } from 'vue'
import { computed, defineComponent } from 'vue'
import version from '../version'
import type { AliasToken, GlobalToken, MapToken, OverrideToken, PresetColorKey, PresetColorType, SeedToken } from './interface'
import { PresetColors } from './interface'
import defaultDerivative from './themes/default'
import defaultSeedToken from './themes/seed'
import formatToken from './util/alias'
import type { FullToken } from './util/genComponentStyleHook'
import genComponentStyleHook from './util/genComponentStyleHook'
import statisticToken, { merge as mergeToken, statistic } from './util/statistic'
const defaultTheme = createTheme(defaultDerivative)
export {
// colors
PresetColors,
// Statistic
statistic,
statisticToken,
mergeToken,
// hooks
useStyleRegister,
genComponentStyleHook
}
export type {
SeedToken,
AliasToken,
PresetColorType,
PresetColorKey,
// FIXME: Remove this type
FullToken,
AliasToken as DerivativeToken
}
// ================================ Context =================================
// To ensure snapshot stable. We disable hashed in test env.
export const defaultConfig: DesignTokenConfig = {
token: defaultSeedToken,
hashed: true
}
export interface DesignTokenConfig {
token: Partial<AliasToken>
theme?: Theme<SeedToken, MapToken>
components?: OverrideToken
hashed?: string | boolean
}
const [useDesignTokenProvider, useDesignTokenInject] = createInjectionState((token: DesignTokenConfig) => {
return token
})
export const DesignTokenProviderContext = defineComponent({
props: {
token: objectType<AliasToken>(),
theme: objectType<Theme<SeedToken, MapToken>>(),
components: objectType<OverrideToken>(),
hashed: someType<string | boolean>([String, Boolean])
},
setup(props, { slots }) {
useDesignTokenProvider(props)
return () => {
return slots.default?.()
}
}
})
export { useDesignTokenProvider }
export const useDesignTokenState = () => useDesignTokenInject() ?? defaultConfig
// ================================== Hook ==================================
export function useToken(): [ComputedRef<Theme<SeedToken, MapToken>>, ComputedRef<GlobalToken>, ComputedRef<string>] {
const designTokenContext = useDesignTokenState()
const salt = computed(() => `${version}-${designTokenContext.hashed || ''}`)
const mergedTheme = computed(() => designTokenContext.theme || defaultTheme)
const tokens = computed(() => [defaultSeedToken, designTokenContext.token])
const opt = computed(() => {
return {
salt: salt.value,
override: { override: designTokenContext.token, ...designTokenContext.components },
formatToken
}
})
const cacheToken = useCacheToken<GlobalToken, SeedToken>(mergedTheme, tokens, opt)
return [mergedTheme, computed(() => cacheToken.value?.[0]), computed(() => (designTokenContext.hashed ? cacheToken.value?.[1] : ''))]
}
export type UseComponentStyleResult = [(node: VNodeChild) => VNodeChild, ComputedRef<string>]
export type GenerateStyle<ComponentToken extends object = AliasToken, ReturnType = CSSInterpolation> = (token: ComponentToken) => ReturnType

View File

@ -0,0 +1,17 @@
import type { ColorNeutralMapToken } from '../interface'
export interface ColorMap {
1: string
2: string
3: string
4: string
5: string
6: string
7: string
8: string
9: string
10: string
}
export type GenerateColorMap = (baseColor: string) => ColorMap
export type GenerateNeutralColorMap = (bgBaseColor: string, textBaseColor: string) => ColorNeutralMapToken

View File

@ -0,0 +1,19 @@
import type { SeedToken, SizeMapToken } from '../../interface'
export default function genSizeMapToken(token: SeedToken): SizeMapToken {
const { sizeUnit, sizeStep } = token
const compactSizeStep = sizeStep - 2
return {
sizeXXL: sizeUnit * (compactSizeStep + 10),
sizeXL: sizeUnit * (compactSizeStep + 6),
sizeLG: sizeUnit * (compactSizeStep + 2),
sizeMD: sizeUnit * (compactSizeStep + 2),
sizeMS: sizeUnit * (compactSizeStep + 1),
size: sizeUnit * compactSizeStep,
sizeSM: sizeUnit * compactSizeStep,
sizeXS: sizeUnit * (compactSizeStep - 1),
sizeXXS: sizeUnit * (compactSizeStep - 1)
}
}

View File

@ -0,0 +1,27 @@
import type { DerivativeFunc } from '@antd-tiny-vue/cssinjs'
import genControlHeight from '../shared/genControlHeight'
import type { MapToken, SeedToken } from '../../interface'
import defaultAlgorithm from '../default'
import genFontMapToken from '../shared/genFontMapToken'
import genCompactSizeMapToken from './genCompactSizeMapToken'
const derivative: DerivativeFunc<SeedToken, MapToken> = (token, mapToken) => {
const mergedMapToken = mapToken ?? defaultAlgorithm(token)
const fontSize = mergedMapToken.fontSizeSM // Smaller size font-size as base
const controlHeight = mergedMapToken.controlHeight - 4
return {
...mergedMapToken,
...genCompactSizeMapToken(mapToken ?? token),
// font
...genFontMapToken(fontSize),
// controlHeight
controlHeight,
...genControlHeight({ ...mergedMapToken, controlHeight })
}
}
export default derivative

View File

@ -0,0 +1,8 @@
import { TinyColor } from '@ctrl/tinycolor'
export const getAlphaColor = (baseColor: string, alpha: number) => new TinyColor(baseColor).setAlpha(alpha).toRgbString()
export const getSolidColor = (baseColor: string, brightness: number) => {
const instance = new TinyColor(baseColor)
return instance.lighten(brightness).toHexString()
}

View File

@ -0,0 +1,50 @@
import { generate } from '@ant-design/colors'
import type { GenerateColorMap, GenerateNeutralColorMap } from '../ColorMap'
import { getAlphaColor, getSolidColor } from './colorAlgorithm'
export const generateColorPalettes: GenerateColorMap = (baseColor: string) => {
const colors = generate(baseColor, { theme: 'dark' })
return {
1: colors[0],
2: colors[1],
3: colors[2],
4: colors[3],
5: colors[6],
6: colors[5],
7: colors[4],
8: colors[6],
9: colors[5],
10: colors[4]
// 8: colors[9],
// 9: colors[8],
// 10: colors[7],
}
}
export const generateNeutralColorPalettes: GenerateNeutralColorMap = (bgBaseColor: string, textBaseColor: string) => {
const colorBgBase = bgBaseColor || '#000'
const colorTextBase = textBaseColor || '#fff'
return {
colorBgBase,
colorTextBase,
colorText: getAlphaColor(colorTextBase, 0.85),
colorTextSecondary: getAlphaColor(colorTextBase, 0.65),
colorTextTertiary: getAlphaColor(colorTextBase, 0.45),
colorTextQuaternary: getAlphaColor(colorTextBase, 0.25),
colorFill: getAlphaColor(colorTextBase, 0.18),
colorFillSecondary: getAlphaColor(colorTextBase, 0.12),
colorFillTertiary: getAlphaColor(colorTextBase, 0.08),
colorFillQuaternary: getAlphaColor(colorTextBase, 0.04),
colorBgElevated: getSolidColor(colorBgBase, 12),
colorBgContainer: getSolidColor(colorBgBase, 8),
colorBgLayout: getSolidColor(colorBgBase, 0),
colorBgSpotlight: getSolidColor(colorBgBase, 26),
colorBorder: getSolidColor(colorBgBase, 26),
colorBorderSecondary: getSolidColor(colorBgBase, 19)
}
}

View File

@ -0,0 +1,43 @@
import { generate } from '@ant-design/colors'
import type { DerivativeFunc } from '@antd-tiny-vue/cssinjs'
import type { ColorPalettes, MapToken, PresetColorType, SeedToken } from '../../interface'
import { defaultPresetColors } from '../seed'
import genColorMapToken from '../shared/genColorMapToken'
import defaultAlgorithm from '../default'
import { generateColorPalettes, generateNeutralColorPalettes } from './colors'
const derivative: DerivativeFunc<SeedToken, MapToken> = (token, mapToken) => {
const colorPalettes = Object.keys(defaultPresetColors)
// @ts-expect-error this is a bug of typescript
.map((colorKey: keyof PresetColorType) => {
const colors = generate(token[colorKey], { theme: 'dark' })
return new Array(10).fill(1).reduce((prev, _, i) => {
prev[`${colorKey}-${i + 1}`] = colors[i]
return prev
}, {}) as ColorPalettes
})
.reduce((prev, cur) => {
prev = {
...prev,
...cur
}
return prev
}, {} as ColorPalettes)
const mergedMapToken = mapToken ?? defaultAlgorithm(token)
return {
...mergedMapToken,
// Dark tokens
...colorPalettes,
// Colors
...genColorMapToken(token, {
generateColorPalettes,
generateNeutralColorPalettes
})
}
}
export default derivative

View File

@ -0,0 +1,8 @@
import { TinyColor } from '@ctrl/tinycolor'
export const getAlphaColor = (baseColor: string, alpha: number) => new TinyColor(baseColor).setAlpha(alpha).toRgbString()
export const getSolidColor = (baseColor: string, brightness: number) => {
const instance = new TinyColor(baseColor)
return instance.darken(brightness).toHexString()
}

View File

@ -0,0 +1,50 @@
import { generate } from '@ant-design/colors'
import type { GenerateColorMap, GenerateNeutralColorMap } from '../ColorMap'
import { getAlphaColor, getSolidColor } from './colorAlgorithm'
export const generateColorPalettes: GenerateColorMap = (baseColor: string) => {
const colors = generate(baseColor)
return {
1: colors[0],
2: colors[1],
3: colors[2],
4: colors[3],
5: colors[4],
6: colors[5],
7: colors[6],
8: colors[4],
9: colors[5],
10: colors[6]
// 8: colors[7],
// 9: colors[8],
// 10: colors[9],
}
}
export const generateNeutralColorPalettes: GenerateNeutralColorMap = (bgBaseColor: string, textBaseColor: string) => {
const colorBgBase = bgBaseColor || '#fff'
const colorTextBase = textBaseColor || '#000'
return {
colorBgBase,
colorTextBase,
colorText: getAlphaColor(colorTextBase, 0.88),
colorTextSecondary: getAlphaColor(colorTextBase, 0.65),
colorTextTertiary: getAlphaColor(colorTextBase, 0.45),
colorTextQuaternary: getAlphaColor(colorTextBase, 0.25),
colorFill: getAlphaColor(colorTextBase, 0.15),
colorFillSecondary: getAlphaColor(colorTextBase, 0.06),
colorFillTertiary: getAlphaColor(colorTextBase, 0.04),
colorFillQuaternary: getAlphaColor(colorTextBase, 0.02),
colorBgLayout: getSolidColor(colorBgBase, 4),
colorBgContainer: getSolidColor(colorBgBase, 0),
colorBgElevated: getSolidColor(colorBgBase, 0),
colorBgSpotlight: getAlphaColor(colorTextBase, 0.85),
colorBorder: getSolidColor(colorBgBase, 15),
colorBorderSecondary: getSolidColor(colorBgBase, 6)
}
}

View File

@ -0,0 +1,52 @@
import { generate } from '@ant-design/colors'
import genControlHeight from '../shared/genControlHeight'
import genSizeMapToken from '../shared/genSizeMapToken'
import type {
ColorPalettes,
MapToken,
PresetColorType,
SeedToken
} from '../../interface'
import { defaultPresetColors } from '../seed'
import genColorMapToken from '../shared/genColorMapToken'
import genCommonMapToken from '../shared/genCommonMapToken'
import genFontMapToken from '../shared/genFontMapToken'
import { generateColorPalettes, generateNeutralColorPalettes } from './colors'
export default function derivative(token: SeedToken): MapToken {
const colorPalettes = Object.keys(defaultPresetColors)
.map((value) => {
const colorKey = value as keyof PresetColorType
const colors = generate(token[colorKey])
return new Array(10).fill(1).reduce((prev, _, i) => {
prev[`${colorKey}-${i + 1}`] = colors[i]
return prev
}, {}) as ColorPalettes
})
.reduce((prev, cur) => {
prev = {
...prev,
...cur
}
return prev
}, {} as ColorPalettes)
return {
...token,
...colorPalettes,
// Colors
...genColorMapToken(token, {
generateColorPalettes,
generateNeutralColorPalettes
}),
// Font
...genFontMapToken(token.fontSize),
// Size
...genSizeMapToken(token),
// Height
...genControlHeight(token),
// Others
...genCommonMapToken(token)
}
}

View File

@ -0,0 +1,77 @@
import type { PresetColorType, SeedToken } from '../internal'
export const defaultPresetColors: PresetColorType = {
blue: '#1677ff',
purple: '#722ED1',
cyan: '#13C2C2',
green: '#52C41A',
magenta: '#EB2F96',
pink: '#eb2f96',
red: '#F5222D',
orange: '#FA8C16',
yellow: '#FADB14',
volcano: '#FA541C',
geekblue: '#2F54EB',
gold: '#FAAD14',
lime: '#A0D911'
}
const seedToken: SeedToken = {
// preset color palettes
...defaultPresetColors,
// Color
colorPrimary: '#1677ff',
colorSuccess: '#52c41a',
colorWarning: '#faad14',
colorError: '#ff4d4f',
colorInfo: '#1677ff',
colorTextBase: '',
colorBgBase: '',
// Font
fontFamily: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji'`,
fontFamilyCode: `'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace`,
fontSize: 14,
// Line
lineWidth: 1,
lineType: 'solid',
// Motion
motionUnit: 0.1,
motionBase: 0,
motionEaseOutCirc: 'cubic-bezier(0.08, 0.82, 0.17, 1)',
motionEaseInOutCirc: 'cubic-bezier(0.78, 0.14, 0.15, 0.86)',
motionEaseOut: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
motionEaseInOut: 'cubic-bezier(0.645, 0.045, 0.355, 1)',
motionEaseOutBack: 'cubic-bezier(0.12, 0.4, 0.29, 1.46)',
motionEaseInBack: 'cubic-bezier(0.71, -0.46, 0.88, 0.6)',
motionEaseInQuint: 'cubic-bezier(0.755, 0.05, 0.855, 0.06)',
motionEaseOutQuint: 'cubic-bezier(0.23, 1, 0.32, 1)',
// Radius
borderRadius: 6,
// Size
sizeUnit: 4,
sizeStep: 4,
sizePopupArrow: 16,
// Control Base
controlHeight: 32,
// zIndex
zIndexBase: 0,
zIndexPopupBase: 1000,
// Image
opacityImage: 1,
// Wireframe
wireframe: false
}
export default seedToken

View File

@ -0,0 +1,81 @@
import { TinyColor } from '@ctrl/tinycolor'
import type { ColorMapToken, SeedToken } from '../../interface'
import type { GenerateColorMap, GenerateNeutralColorMap } from '../ColorMap'
interface PaletteGenerators {
generateColorPalettes: GenerateColorMap
generateNeutralColorPalettes: GenerateNeutralColorMap
}
export default function genColorMapToken(seed: SeedToken, { generateColorPalettes, generateNeutralColorPalettes }: PaletteGenerators): ColorMapToken {
const { colorSuccess: colorSuccessBase, colorWarning: colorWarningBase, colorError: colorErrorBase, colorInfo: colorInfoBase, colorPrimary: colorPrimaryBase, colorBgBase, colorTextBase } = seed
const primaryColors = generateColorPalettes(colorPrimaryBase)
const successColors = generateColorPalettes(colorSuccessBase)
const warningColors = generateColorPalettes(colorWarningBase)
const errorColors = generateColorPalettes(colorErrorBase)
const infoColors = generateColorPalettes(colorInfoBase)
const neutralColors = generateNeutralColorPalettes(colorBgBase, colorTextBase)
return {
...neutralColors,
colorPrimaryBg: primaryColors[1],
colorPrimaryBgHover: primaryColors[2],
colorPrimaryBorder: primaryColors[3],
colorPrimaryBorderHover: primaryColors[4],
colorPrimaryHover: primaryColors[5],
colorPrimary: primaryColors[6],
colorPrimaryActive: primaryColors[7],
colorPrimaryTextHover: primaryColors[8],
colorPrimaryText: primaryColors[9],
colorPrimaryTextActive: primaryColors[10],
colorSuccessBg: successColors[1],
colorSuccessBgHover: successColors[2],
colorSuccessBorder: successColors[3],
colorSuccessBorderHover: successColors[4],
colorSuccessHover: successColors[4],
colorSuccess: successColors[6],
colorSuccessActive: successColors[7],
colorSuccessTextHover: successColors[8],
colorSuccessText: successColors[9],
colorSuccessTextActive: successColors[10],
colorErrorBg: errorColors[1],
colorErrorBgHover: errorColors[2],
colorErrorBorder: errorColors[3],
colorErrorBorderHover: errorColors[4],
colorErrorHover: errorColors[5],
colorError: errorColors[6],
colorErrorActive: errorColors[7],
colorErrorTextHover: errorColors[8],
colorErrorText: errorColors[9],
colorErrorTextActive: errorColors[10],
colorWarningBg: warningColors[1],
colorWarningBgHover: warningColors[2],
colorWarningBorder: warningColors[3],
colorWarningBorderHover: warningColors[4],
colorWarningHover: warningColors[4],
colorWarning: warningColors[6],
colorWarningActive: warningColors[7],
colorWarningTextHover: warningColors[8],
colorWarningText: warningColors[9],
colorWarningTextActive: warningColors[10],
colorInfoBg: infoColors[1],
colorInfoBgHover: infoColors[2],
colorInfoBorder: infoColors[3],
colorInfoBorderHover: infoColors[4],
colorInfoHover: infoColors[4],
colorInfo: infoColors[6],
colorInfoActive: infoColors[7],
colorInfoTextHover: infoColors[8],
colorInfoText: infoColors[9],
colorInfoTextActive: infoColors[10],
colorBgMask: new TinyColor('#000').setAlpha(0.45).toRgbString(),
colorWhite: '#fff'
}
}

View File

@ -0,0 +1,19 @@
import type { CommonMapToken, SeedToken } from '../../interface'
import genRadius from './genRadius'
export default function genCommonMapToken(token: SeedToken): CommonMapToken {
const { motionUnit, motionBase, borderRadius, lineWidth } = token
return {
// motion
motionDurationFast: `${(motionBase + motionUnit).toFixed(1)}s`,
motionDurationMid: `${(motionBase + motionUnit * 2).toFixed(1)}s`,
motionDurationSlow: `${(motionBase + motionUnit * 3).toFixed(1)}s`,
// line
lineWidthBold: lineWidth + 1,
// radius
...genRadius(borderRadius)
}
}

View File

@ -0,0 +1,13 @@
import type { HeightMapToken, SeedToken } from '../../interface'
const genControlHeight = (token: SeedToken): HeightMapToken => {
const { controlHeight } = token
return {
controlHeightSM: controlHeight * 0.75,
controlHeightXS: controlHeight * 0.5,
controlHeightLG: controlHeight * 1.25
}
}
export default genControlHeight

View File

@ -0,0 +1,33 @@
import type { FontMapToken } from '../../interface'
import genFontSizes from './genFontSizes'
const genFontMapToken = (fontSize: number): FontMapToken => {
const fontSizePairs = genFontSizes(fontSize)
const fontSizes = fontSizePairs.map(pair => pair.size)
const lineHeights = fontSizePairs.map(pair => pair.lineHeight)
return {
fontSizeSM: fontSizes[0],
fontSize: fontSizes[1],
fontSizeLG: fontSizes[2],
fontSizeXL: fontSizes[3],
fontSizeHeading1: fontSizes[6],
fontSizeHeading2: fontSizes[5],
fontSizeHeading3: fontSizes[4],
fontSizeHeading4: fontSizes[3],
fontSizeHeading5: fontSizes[2],
lineHeight: lineHeights[1],
lineHeightLG: lineHeights[2],
lineHeightSM: lineHeights[0],
lineHeightHeading1: lineHeights[6],
lineHeightHeading2: lineHeights[5],
lineHeightHeading3: lineHeights[4],
lineHeightHeading4: lineHeights[3],
lineHeightHeading5: lineHeights[2]
}
}
export default genFontMapToken

View File

@ -0,0 +1,22 @@
// https://zhuanlan.zhihu.com/p/32746810
export default function getFontSizes(base: number) {
const fontSizes = new Array(10).fill(null).map((_, index) => {
const i = index - 1
const baseSize = base * 2.71828 ** (i / 5)
const intSize = index > 1 ? Math.floor(baseSize) : Math.ceil(baseSize)
// Convert to even
return Math.floor(intSize / 2) * 2
})
fontSizes[1] = base
return fontSizes.map(size => {
const height = size + 8
return {
size,
lineHeight: height / size
}
})
}

View File

@ -0,0 +1,54 @@
import type { MapToken } from '../../interface'
const genRadius = (radiusBase: number): Pick<MapToken, 'borderRadiusXS' | 'borderRadiusSM' | 'borderRadiusLG' | 'borderRadius' | 'borderRadiusOuter'> => {
let radiusLG = radiusBase
let radiusSM = radiusBase
let radiusXS = radiusBase
let radiusOuter = radiusBase
// radiusLG
if (radiusBase < 6 && radiusBase >= 5) {
radiusLG = radiusBase + 1
} else if (radiusBase < 16 && radiusBase >= 6) {
radiusLG = radiusBase + 2
} else if (radiusBase >= 16) {
radiusLG = 16
}
// radiusSM
if (radiusBase < 7 && radiusBase >= 5) {
radiusSM = 4
} else if (radiusBase < 8 && radiusBase >= 7) {
radiusSM = 5
} else if (radiusBase < 14 && radiusBase >= 8) {
radiusSM = 6
} else if (radiusBase < 16 && radiusBase >= 14) {
radiusSM = 7
} else if (radiusBase >= 16) {
radiusSM = 8
}
// radiusXS
if (radiusBase < 6 && radiusBase >= 2) {
radiusXS = 1
} else if (radiusBase >= 6) {
radiusXS = 2
}
// radiusOuter
if (radiusBase > 4 && radiusBase < 8) {
radiusOuter = 4
} else if (radiusBase >= 8) {
radiusOuter = 6
}
return {
borderRadius: radiusBase > 16 ? 16 : radiusBase,
borderRadiusXS: radiusXS,
borderRadiusSM: radiusSM,
borderRadiusLG: radiusLG,
borderRadiusOuter: radiusOuter
}
}
export default genRadius

View File

@ -0,0 +1,17 @@
import type { SeedToken, SizeMapToken } from '../../interface'
export default function genSizeMapToken(token: SeedToken): SizeMapToken {
const { sizeUnit, sizeStep } = token
return {
sizeXXL: sizeUnit * (sizeStep + 8), // 48
sizeXL: sizeUnit * (sizeStep + 4), // 32
sizeLG: sizeUnit * (sizeStep + 2), // 24
sizeMD: sizeUnit * (sizeStep + 1), // 20
sizeMS: sizeUnit * sizeStep, // 16
size: sizeUnit * sizeStep, // 16
sizeSM: sizeUnit * (sizeStep - 1), // 12
sizeXS: sizeUnit * (sizeStep - 2), // 8
sizeXXS: sizeUnit * (sizeStep - 3) // 4
}
}

View File

@ -0,0 +1,196 @@
import { TinyColor } from '@ctrl/tinycolor'
import type { AliasToken, MapToken, OverrideToken, SeedToken } from '../interface'
import seedToken from '../themes/seed'
import getAlphaColor from './getAlphaColor'
/** Raw merge of `@antd-tiny-vue/cssinjs` token. Which need additional process */
type RawMergedToken = MapToken & OverrideToken & { override: Partial<AliasToken> }
/**
* Seed (designer) > Derivative (designer) > Alias (developer).
*
* Merge seed & derivative & override token and generate alias token for developer.
*/
export default function formatToken(derivativeToken: RawMergedToken): AliasToken {
const { override, ...restToken } = derivativeToken
const overrideTokens = { ...override }
Object.keys(seedToken).forEach(token => {
delete overrideTokens[token as keyof SeedToken]
})
const mergedToken = {
...restToken,
...overrideTokens
}
const screenXS = 480
const screenSM = 576
const screenMD = 768
const screenLG = 992
const screenXL = 1200
const screenXXL = 1600
// Generate alias token
const aliasToken: AliasToken = {
...mergedToken,
colorLink: mergedToken.colorInfoText,
colorLinkHover: mergedToken.colorInfoHover,
colorLinkActive: mergedToken.colorInfoActive,
// ============== Background ============== //
colorFillContent: mergedToken.colorFillSecondary,
colorFillContentHover: mergedToken.colorFill,
colorFillAlter: mergedToken.colorFillQuaternary,
colorBgContainerDisabled: mergedToken.colorFillTertiary,
// ============== Split ============== //
colorBorderBg: mergedToken.colorBgContainer,
colorSplit: getAlphaColor(mergedToken.colorBorderSecondary, mergedToken.colorBgContainer),
// ============== Text ============== //
colorTextPlaceholder: mergedToken.colorTextQuaternary,
colorTextDisabled: mergedToken.colorTextQuaternary,
colorTextHeading: mergedToken.colorText,
colorTextLabel: mergedToken.colorTextSecondary,
colorTextDescription: mergedToken.colorTextTertiary,
colorTextLightSolid: mergedToken.colorWhite,
colorHighlight: mergedToken.colorError,
colorBgTextHover: mergedToken.colorFillSecondary,
colorBgTextActive: mergedToken.colorFill,
colorIcon: mergedToken.colorTextTertiary,
colorIconHover: mergedToken.colorText,
colorErrorOutline: getAlphaColor(mergedToken.colorErrorBg, mergedToken.colorBgContainer),
colorWarningOutline: getAlphaColor(mergedToken.colorWarningBg, mergedToken.colorBgContainer),
// Font
fontSizeIcon: mergedToken.fontSizeSM,
// Control
lineWidth: mergedToken.lineWidth,
controlOutlineWidth: mergedToken.lineWidth * 2,
// Checkbox size and expand icon size
controlInteractiveSize: mergedToken.controlHeight / 2,
controlItemBgHover: mergedToken.colorFillTertiary,
controlItemBgActive: mergedToken.colorPrimaryBg,
controlItemBgActiveHover: mergedToken.colorPrimaryBgHover,
controlItemBgActiveDisabled: mergedToken.colorFill,
controlTmpOutline: mergedToken.colorFillQuaternary,
controlOutline: getAlphaColor(mergedToken.colorPrimaryBg, mergedToken.colorBgContainer),
lineType: mergedToken.lineType,
borderRadius: mergedToken.borderRadius,
borderRadiusXS: mergedToken.borderRadiusXS,
borderRadiusSM: mergedToken.borderRadiusSM,
borderRadiusLG: mergedToken.borderRadiusLG,
fontWeightStrong: 600,
opacityLoading: 0.65,
linkDecoration: 'none',
linkHoverDecoration: 'none',
linkFocusDecoration: 'none',
controlPaddingHorizontal: 12,
controlPaddingHorizontalSM: 8,
paddingXXS: mergedToken.sizeXXS,
paddingXS: mergedToken.sizeXS,
paddingSM: mergedToken.sizeSM,
padding: mergedToken.size,
paddingMD: mergedToken.sizeMD,
paddingLG: mergedToken.sizeLG,
paddingXL: mergedToken.sizeXL,
paddingContentHorizontalLG: mergedToken.sizeLG,
paddingContentVerticalLG: mergedToken.sizeMS,
paddingContentHorizontal: mergedToken.sizeMS,
paddingContentVertical: mergedToken.sizeSM,
paddingContentHorizontalSM: mergedToken.size,
paddingContentVerticalSM: mergedToken.sizeXS,
marginXXS: mergedToken.sizeXXS,
marginXS: mergedToken.sizeXS,
marginSM: mergedToken.sizeSM,
margin: mergedToken.size,
marginMD: mergedToken.sizeMD,
marginLG: mergedToken.sizeLG,
marginXL: mergedToken.sizeXL,
marginXXL: mergedToken.sizeXXL,
boxShadow: `
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 9px 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowSecondary: `
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 9px 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowTertiary: `
0 1px 2px 0 rgba(0, 0, 0, 0.03),
0 1px 6px -1px rgba(0, 0, 0, 0.02),
0 2px 4px 0 rgba(0, 0, 0, 0.02)
`,
screenXS,
screenXSMin: screenXS,
screenXSMax: screenSM - 1,
screenSM,
screenSMMin: screenSM,
screenSMMax: screenMD - 1,
screenMD,
screenMDMin: screenMD,
screenMDMax: screenLG - 1,
screenLG,
screenLGMin: screenLG,
screenLGMax: screenXL - 1,
screenXL,
screenXLMin: screenXL,
screenXLMax: screenXXL - 1,
screenXXL,
screenXXLMin: screenXXL,
boxShadowPopoverArrow: '2px 2px 5px rgba(0, 0, 0, 0.05)',
boxShadowCard: `
0 1px 2px -2px ${new TinyColor('rgba(0, 0, 0, 0.16)').toRgbString()},
0 3px 6px 0 ${new TinyColor('rgba(0, 0, 0, 0.12)').toRgbString()},
0 5px 12px 4px ${new TinyColor('rgba(0, 0, 0, 0.09)').toRgbString()}
`,
boxShadowDrawerRight: `
-6px 0 16px 0 rgba(0, 0, 0, 0.08),
-3px 0 6px -4px rgba(0, 0, 0, 0.12),
-9px 0 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowDrawerLeft: `
6px 0 16px 0 rgba(0, 0, 0, 0.08),
3px 0 6px -4px rgba(0, 0, 0, 0.12),
9px 0 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowDrawerUp: `
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 9px 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowDrawerDown: `
0 -6px 16px 0 rgba(0, 0, 0, 0.08),
0 -3px 6px -4px rgba(0, 0, 0, 0.12),
0 -9px 28px 8px rgba(0, 0, 0, 0.05)
`,
boxShadowTabsOverflowLeft: 'inset 10px 0 8px -8px rgba(0, 0, 0, 0.08)',
boxShadowTabsOverflowRight: 'inset -10px 0 8px -8px rgba(0, 0, 0, 0.08)',
boxShadowTabsOverflowTop: 'inset 0 10px 8px -8px rgba(0, 0, 0, 0.08)',
boxShadowTabsOverflowBottom: 'inset 0 -10px 8px -8px rgba(0, 0, 0, 0.08)',
// Override AliasToken
...overrideTokens
}
return aliasToken
}

View File

@ -0,0 +1,98 @@
/* eslint-disable no-redeclare */
import type { CSSInterpolation } from '@antd-tiny-vue/cssinjs'
import { useStyleRegister } from '@antd-tiny-vue/cssinjs'
import type { Ref } from 'vue'
import { computed } from 'vue'
import { genCommonStyle, genLinkStyle } from '../../style'
import { useProviderConfigState } from '../../config-provider/context'
import type { UseComponentStyleResult } from '../internal'
import { mergeToken, statisticToken, useToken } from '../internal'
import type { ComponentTokenMap, GlobalToken } from '../interface'
export type OverrideTokenWithoutDerivative = ComponentTokenMap
export type OverrideComponent = keyof OverrideTokenWithoutDerivative
export type GlobalTokenWithComponent<ComponentName extends OverrideComponent> = GlobalToken & ComponentTokenMap[ComponentName]
export interface StyleInfo<ComponentName extends OverrideComponent> {
hashId: string
prefixCls: string
rootPrefixCls: string
iconPrefixCls: string
overrideComponentToken: ComponentTokenMap[ComponentName]
}
export type TokenWithCommonCls<T> = T & {
/** Wrap component class with `.` prefix */
componentCls: string
/** Origin prefix which do not have `.` prefix */
prefixCls: string
/** Wrap icon class with `.` prefix */
iconCls: string
/** Wrap ant prefixCls class with `.` prefix */
antCls: string
}
export type FullToken<ComponentName extends OverrideComponent> = TokenWithCommonCls<GlobalTokenWithComponent<ComponentName>>
export default function genComponentStyleHook<ComponentName extends OverrideComponent>(
component: ComponentName,
styleFn: (token: FullToken<ComponentName>, info: StyleInfo<ComponentName>) => CSSInterpolation,
getDefaultToken?: OverrideTokenWithoutDerivative[ComponentName] | ((token: GlobalToken) => OverrideTokenWithoutDerivative[ComponentName])
) {
return (prefixCls: Ref<string>): UseComponentStyleResult => {
const [theme, token, hashId] = useToken()
const { getPrefixCls, iconPrefixCls } = useProviderConfigState()
const rootPrefixCls = computed(() => getPrefixCls())
const sharedInfo = computed(() => {
return {
theme: theme.value,
token: token.value,
hashId: hashId.value,
path: ['Shared', rootPrefixCls.value]
}
})
// Generate style for all a tags in antd component.
useStyleRegister(sharedInfo, () => [
{
// Link
'&': genLinkStyle(token.value)
}
])
const componentInfo = computed(() => ({
theme: theme.value,
token: token.value,
hashId: hashId.value,
path: [component, prefixCls.value, iconPrefixCls.value]
}))
return [
useStyleRegister(componentInfo, () => {
const { token: proxyToken, flush } = statisticToken(token.value)
const defaultComponentToken = typeof getDefaultToken === 'function' ? getDefaultToken(proxyToken) : getDefaultToken
const mergedComponentToken = { ...defaultComponentToken, ...token.value[component] }
const componentCls = `.${prefixCls.value}`
const mergedToken = mergeToken<TokenWithCommonCls<GlobalTokenWithComponent<OverrideComponent>>>(
proxyToken,
{
componentCls,
prefixCls: prefixCls.value,
iconCls: `.${iconPrefixCls.value}`,
antCls: `.${rootPrefixCls.value}`
},
mergedComponentToken
)
const styleInterpolation = styleFn(mergedToken as unknown as FullToken<ComponentName>, {
hashId: hashId.value,
prefixCls: prefixCls.value,
rootPrefixCls: rootPrefixCls.value,
iconPrefixCls: iconPrefixCls.value,
overrideComponentToken: token.value[component]
})
flush(component, mergedComponentToken)
return [genCommonStyle(token.value, prefixCls.value), styleInterpolation]
}),
hashId
]
}
}

View File

@ -0,0 +1,29 @@
import { TinyColor } from '@ctrl/tinycolor'
function isStableColor(color: number): boolean {
return color >= 0 && color <= 255
}
function getAlphaColor(frontColor: string, backgroundColor: string): string {
const { r: fR, g: fG, b: fB, a: originAlpha } = new TinyColor(frontColor).toRgb()
if (originAlpha < 1) {
return frontColor
}
const { r: bR, g: bG, b: bB } = new TinyColor(backgroundColor).toRgb()
for (let fA = 0.01; fA <= 1; fA += 0.01) {
const r = Math.round((fR - bR * (1 - fA)) / fA)
const g = Math.round((fG - bG * (1 - fA)) / fA)
const b = Math.round((fB - bB * (1 - fA)) / fA)
if (isStableColor(r) && isStableColor(g) && isStableColor(b)) {
return new TinyColor({ r, g, b, a: Math.round(fA * 100) / 100 }).toRgbString()
}
}
// fallback
/* istanbul ignore next */
return new TinyColor({ r: fR, g: fG, b: fB, a: 1 }).toRgbString()
}
export default getAlphaColor

View File

@ -0,0 +1,70 @@
declare const CSSINJS_STATISTIC: any
const enableStatistic = process.env.NODE_ENV !== 'production' || typeof CSSINJS_STATISTIC !== 'undefined'
let recording = true
/**
* This function will do as `Object.assign` in production. But will use Object.defineProperty:get to
* pass all value access in development. To support statistic field usage with alias token.
*/
export function merge<T extends object>(...objs: Partial<T>[]): T {
/* istanbul ignore next */
if (!enableStatistic) {
return Object.assign({}, ...objs)
}
recording = false
const ret = {} as T
objs.forEach(obj => {
const keys = Object.keys(obj)
keys.forEach(key => {
Object.defineProperty(ret, key, {
configurable: true,
enumerable: true,
get: () => (obj as any)[key]
})
})
})
recording = true
return ret
}
/** @private Internal Usage. Not use in your production. */
export const statistic: Record<string, { global: string[]; component: Record<string, string | number> }> = {}
/** @private Internal Usage. Not use in your production. */
// eslint-disable-next-line camelcase
export const _statistic_build_: typeof statistic = {}
/* istanbul ignore next */
function noop() {}
/** Statistic token usage case. Should use `merge` function if you do not want spread record. */
export default function statisticToken<T extends object>(token: T) {
let tokenKeys: Set<string> | undefined
let proxy = token
let flush: (componentName: string, componentToken: Record<string, string | number>) => void = noop
if (enableStatistic) {
tokenKeys = new Set<string>()
proxy = new Proxy(token, {
get(obj: any, prop: any) {
if (recording) {
tokenKeys!.add(prop)
}
return obj[prop]
}
})
flush = (componentName, componentToken) => {
statistic[componentName] = { global: Array.from(tokenKeys!), component: componentToken }
}
}
return { token: proxy, keys: tokenKeys, flush }
}

1
components/version.ts Normal file
View File

@ -0,0 +1 @@
export default '0.0.1'

View File

@ -1,30 +1,77 @@
{
"name": "antd-tiny-vue",
"version": "1.0.0",
"description": "",
"keywords": [],
"packageManager": "pnpm@8.2.0",
"description": "this is a tiny antd components for vue3",
"keywords": [
"antd",
"ant-design-vue",
"ant-design",
"vue3",
"vue3-components",
"components"
],
"license": "MIT",
"author": "aibayanyu",
"main": "index.js",
"main": "lib/index.js",
"module": "es/index.js",
"types": "lib/index.d.ts",
"files": [
"lib",
"es",
"dist",
"README.md"
],
"scripts": {
"test": "vitest",
"prepare": "husky install"
"prepare": "husky install",
"dev": "vitepress dev",
"build:site": "vitepress build",
"build:lib": "vite build --config vite.build.config.ts",
"build:umd": "vite build --config vite.bundle.config.ts",
"copy:css": "cpx \"components/style/*.css\" dist",
"preview": "vitepress preview"
},
"dependencies": {
"@ant-design/colors": "^7.0.0",
"@ant-design/icons-vue": "^6.1.0",
"@antd-tiny-vue/cssinjs": "0.0.8",
"@ctrl/tinycolor": "^3.6.0",
"@v-c/utils": "^0.0.22",
"@vueuse/core": "^9.13.0",
"vue": "^3.3.0-beta.2"
},
"devDependencies": {
"@commitlint/cli": "^17.4.3",
"@commitlint/config-conventional": "^17.4.3",
"@mistjs/eslint-config-vue-jsx": "^0.0.3",
"@mistjs/tsconfig": "^1.0.0",
"@mistjs/tsconfig-vue": "^0.0.3",
"@types/node": "^18.13.0",
"eslint": "^8.34.0",
"@commitlint/cli": "^17.5.0",
"@commitlint/config-conventional": "^17.4.4",
"@mistjs/eslint-config-vue-jsx": "^0.0.7",
"@mistjs/tsconfig": "^1.1.1",
"@mistjs/tsconfig-vue": "^1.1.2",
"@types/node": "^18.15.10",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"cpx": "^1.5.0",
"eslint": "^8.36.0",
"husky": "^8.0.3",
"lint-staged": "^13.1.2",
"prettier": "^2.8.4",
"typescript": "^4.9.5",
"vite": "^4.1.1",
"lint-staged": "^13.2.0",
"prettier": "^2.8.7",
"typescript": "^5.0.4",
"unbuild": "^1.1.2",
"vite": "^4.3.3",
"vite-plugin-dts": "^2.3.0",
"vite-plugin-vitepress-demo": "2.0.0-beta.29",
"vitepress": "1.0.0-alpha.74",
"vitest": "^0.28.5"
},
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"@algolia/client-search"
]
},
"overrides": {
"vue": "3.3.0-beta.2"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx,vue}": "eslint --fix"
}

4530
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

1
site/components/index.md Normal file
View File

@ -0,0 +1 @@
# Components

View File

@ -0,0 +1 @@
# 组件

Some files were not shown because too many files have changed in this diff Show More