diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx new file mode 100644 index 00000000..ca9b03c8 --- /dev/null +++ b/src/components/Button/Button.tsx @@ -0,0 +1,79 @@ +import { + computed, + defineComponent, + type ExtractPropTypes, + type ExtractPublicPropTypes, + type PropType, + type SlotsType, + type VNode, +} from 'vue' +import { useStyle } from './style' +import { useTheme } from '@/theme' + +const props = { + variant: { + type: String as PropType<'solid' | 'soft' | 'outline' | 'link' | 'subtle'>, + default: 'solid', + }, + color: { + type: String as PropType<'primary' | 'success' | 'warning' | 'error' | ColorKey>, + }, + size: { + type: String as PropType<'xs' | 'sm' | 'md' | 'lg' | 'xl'>, + default: 'md', + }, + ring: { + type: Boolean, + default: true, + }, + rounded: Boolean, + square: Boolean, + circle: Boolean, + block: Boolean, + loading: Boolean, + disabled: Boolean, +} + +export type ButtonProps = ExtractPropTypes + +export type ButtonPublicProps = ExtractPublicPropTypes + +export type ButtonCssVars = { + '--t-btn-text-color': string + '--t-btn-text-color-hover': string + '--t-btn-border-color': string + '--t-btn-border-color-hover': string + '--t-btn-bg': string + '--t-btn-bg-hover': string + '--t-btn-ring-color': string +} + +export const Button = defineComponent({ + name: 'TButton', + props, + emits: { + click: (payload: MouseEvent) => {}, + }, + slots: Object as SlotsType<{ + default: () => VNode + item: { data: number } + }>, + setup(props, { slots, emit }) { + const { getColorKey } = useTheme() + + const { cssVars, cls } = useStyle(() => { + return { + ...props, + rounded: props.rounded || props.circle, + square: props.square || props.circle, + } + }) + + const hasIcon = computed(() => !!slots.icon || props.loading) + const onClick = (e: MouseEvent) => { + if (!props.disabled) { + emit('click', e) + } + } + }, +}) diff --git a/src/components/Button/style.ts b/src/components/Button/style.ts new file mode 100644 index 00000000..f1c1a8dc --- /dev/null +++ b/src/components/Button/style.ts @@ -0,0 +1,104 @@ +import { classed, type VariantProps } from '@tw-classed/core' +import { computed, toValue, type MaybeRefOrGetter, toRef, type Ref } from 'vue' +import type { ButtonCssVars, ButtonProps } from './Button' +import { useTheme, type ColorKey } from '@/theme' +import { COLORS } from '@/theme' + +const createCssVars = (vars: Partial = {}) => { + const result: ButtonCssVars = { + '--t-btn-text-color': vars['--t-btn-text-color'] || COLORS.gray[700], + '--t-btn-text-color-hover': vars['--t-btn-text-color-hover'] || vars['--t-btn-text-color'] || COLORS.gray[700], + '--t-btn-border-color': vars['--t-btn-border-color'] || COLORS.transparent, + '--t-btn-border-color-hover': + vars['--t-btn-border-color-hover'] || vars['--t-btn-border-color'] || COLORS.transparent, + '--t-btn-bg': vars['--t-btn-bg'] || COLORS.white, + '--t-btn-bg-hover': vars['--t-btn-bg-hover'] || vars['--t-btn-bg'] || COLORS.white, + '--t-btn-ring-color': vars['--t-btn-ring-color'] || vars['--t-btn-bg'] || COLORS.indigo[500], + } + return result +} + +const getBtnCssVars = (variant: ButtonProps['variant'], _color?: ColorKey) => { + if (variant === 'link') { + const color = _color || 'gray' + return createCssVars({ + '--t-btn-text-color': COLORS[color][600], + '--t-btn-text-color-hover': COLORS[color][700], + '--t-btn-bg': 'transparent', + }) + } else if (variant === 'subtle') { + const color = _color || 'gray' + return createCssVars({ + '--t-btn-text-color': COLORS[color][600], + '--t-btn-bg': COLORS[color][100], + '--t-btn-bg-hover': COLORS[color][200], + '--t-btn-ring-color': COLORS[color][500], + }) + } else { + const color = _color + return color + ? createCssVars({ + '--t-btn-text-color': COLORS.white, + '--t-btn-bg': COLORS[color][500], + '--t-btn-border-color': COLORS.transparent, + '--t-btn-bg-hover': COLORS[color][600], + '--t-btn-ring-color': COLORS[color][500], + }) + : createCssVars({ + '--t-btn-border-color': COLORS.gray[300], // border.default + '--t-btn-bg-hover': COLORS.gray[50], + '--t-btn-ring-color': COLORS.indigo[500], + }) + } +} + +const createBtnCls = classed('t-button', { + base: `inline-flex text-center justify-center items-center border font-medium + h-[--t-btn-h] + bg-[--t-btn-bg] text-[--t-btn-text-color] border-[--t-btn-border-color] + focus:outline-none + `.replace(/\s+/g, ' '), + variants: { + variant: { + solid: `shadow-sm`, + soft: `shadow-sm`, + outline: `shadow-sm`, + link: ``, + subtle: `shadow-sm`, + }, + size: { + xs: '[--t-btn-h:calc(1.75rem+2px)] h-[--t-btn-h] text-xs/3 px-2', + sm: '[--t-btn-h:calc(2rem+2px)] h-[--t-btn-h] text-xs/4 px-3', + md: '[--t-btn-h:calc(2.25rem+2px)] h-[--t-btn-h] text-sm/5 px-4', + lg: '[--t-btn-h:calc(2.5rem+2px)] h-[--t-btn-h] text-base/6 px-5', + xl: '[--t-btn-h:calc(2.75rem+2px)] h-[--t-btn-h] text-base/7 px-6', + }, + ring: { + true: 'focus:ring-[--t-btn-ring-color] focus:ring-2 focus:ring-offset-2', + false: 'focus-visible:ring-[--t-btn-ring-color] focus-visible:ring-2 focus-visible:ring-offset-2', + }, + rounded: { + true: 'rounded-full', + false: 'rounded-md', + }, + square: { + true: '!px-0 w-[--t-btn-h]', + }, + block: { + true: 'w-full', + }, + disabled: { + true: 'cursor-not-allowed opacity-50', + false: `cursor-pointer hover:bg-[--t-btn-bg-hover] hover:text-[--t-btn-text-color-hover] hover:border-[--t-btn-border-color-hover]`, + }, + }, +}) + +export function useStyle(variant: MaybeRefOrGetter) { + const { getColorKey } = useTheme() + const variantRef = computed(() => toValue(variant)) + return { + cssVars: computed(() => getBtnCssVars(variantRef.value.variant, getColorKey(variantRef.value.color || 'gray'))), + cls: computed(() => createBtnCls(variantRef.value)), + } +} diff --git a/src/components/Button/styles.ts b/src/components/Button/styles.ts index 3d016eb8..e57d2b3a 100644 --- a/src/components/Button/styles.ts +++ b/src/components/Button/styles.ts @@ -2,16 +2,6 @@ import { classed, type VariantProps } from '@tw-classed/core' import { colors, type ColorKey, border } from '@/core/colors' import { computed, toValue, type MaybeRefOrGetter, toRef } from 'vue' -export type ButtonCssVars = { - '--t-btn-text-color': string - '--t-btn-text-color-hover': string - '--t-btn-border-color': string - '--t-btn-border-color-hover': string - '--t-btn-bg': string - '--t-btn-bg-hover': string - '--t-btn-ring-color': string -} - const createBtnVars = (vars: Partial = {}) => { const result: ButtonCssVars = { '--t-btn-text-color': vars['--t-btn-text-color'] || colors.gray[700], diff --git a/src/components/CheckBox/CheckBox.vue b/src/components/CheckBox/CheckBox.vue index 034eef60..b4042596 100644 --- a/src/components/CheckBox/CheckBox.vue +++ b/src/components/CheckBox/CheckBox.vue @@ -2,7 +2,7 @@ import { colors } from '@/core/colors' import { useTheme } from '@/core/theme' import { useControllable } from '@/hooks/controllable' -import { ref, computed } from 'vue' +import { ref, computed, type Component } from 'vue' import { getCssVars } from './styles' defineOptions({ name: 'TCheckBox' }) diff --git a/src/core/theme.ts b/src/core/theme.ts deleted file mode 100644 index 81811bfd..00000000 --- a/src/core/theme.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ref } from 'vue' -import { colors, type ColorKey } from './colors' - -export type Theme = { - colors: { - primary: ColorKey - success: ColorKey - warning: ColorKey - error: ColorKey - } -} - -export type ColorAlias = keyof Theme['colors'] - -export function useTheme() { - const theme = ref({ - colors: { - primary: 'indigo', - success: 'green', - warning: 'amber', - error: 'red', - }, - }) - const getColorKey = (key: ColorAlias | ColorKey = 'primary'): ColorKey => { - if (colors[key as ColorKey]) { - return key as ColorKey - } - return theme.value.colors[key as ColorAlias] || theme.value.colors.primary - } - const getColor = (key: ColorAlias | ColorKey = 'primary') => { - return colors[getColorKey(key)] - } - return { theme, getColorKey, getColor } -} diff --git a/src/styles/index.ts b/src/styles/index.ts new file mode 100644 index 00000000..4d0220dc --- /dev/null +++ b/src/styles/index.ts @@ -0,0 +1,8 @@ + + +export function useCssVars('Radio',()=>{ + return { + '--t-radio-accent-color': string + '--t-radio-ring-color': string + } +}) \ No newline at end of file diff --git a/src/core/colors.ts b/src/theme/colors.ts similarity index 95% rename from src/core/colors.ts rename to src/theme/colors.ts index 163dbd67..3d098dae 100644 --- a/src/core/colors.ts +++ b/src/theme/colors.ts @@ -1,3 +1,5 @@ +export type ColorAlias = 'primary' | 'success' | 'warning' | 'error' + export type ColorKey = | 'slate' | 'gray' @@ -21,10 +23,13 @@ export type ColorKey = | 'fuchsia' | 'pink' | 'rose' + +export type Color = ColorKey | ColorAlias export type ColorLv = '50' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | '950' -export type ColorName = `${ColorKey}.${ColorLv}` +export type ColorPath = `${Color}.${ColorLv}` +export type ColorMap = Record -export const colors = { +export const COLORS = { transparent: 'transparent', black: '#000', white: '#fff', @@ -316,9 +321,6 @@ export const colors = { }, } as const -export const transparent = colors.transparent -export const black = colors.black -export const white = colors.white export const border = { - default: colors.gray[300], + default: COLORS.gray[300], } diff --git a/src/theme/index.ts b/src/theme/index.ts new file mode 100644 index 00000000..96fd668d --- /dev/null +++ b/src/theme/index.ts @@ -0,0 +1,40 @@ +import { computed, ref } from 'vue' +import { type ColorKey, COLORS, type ColorAlias, type ColorMap, type Color } from './colors' + +export { COLORS, type ColorKey } + +export type Theme = { + colors: { + [key in ColorAlias]: ColorKey + } +} + +export function useTheme() { + const theme = ref({ + colors: { + primary: 'indigo', + success: 'green', + warning: 'amber', + error: 'red', + }, + }) + + const aliasColors = computed(() => + (Object.keys(theme.value.colors) as ColorAlias[]).reduce((acc, alias) => { + acc[alias] = COLORS[theme.value.colors[alias]] + return acc + }, {} as { [key in ColorAlias]: ColorMap }) + ) + + const getColorKey = (color: Color) => { + // @ts-ignore + return (theme.value.colors[color] || color) as ColorKey + } + const colors = computed(() => { + return { + ...COLORS, + ...aliasColors.value, + } + }) + return { colors, getColorKey } +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 1a5f0b2c..937cd49c 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -11,3 +11,7 @@ export const PropTypes = { string, symbol, } + +export function emitFn any>(fn: T) { + return fn as (...args: Parameters) => void +} diff --git a/src/utils/style.ts b/src/utils/style.ts new file mode 100644 index 00000000..e69de29b