UNPKG

vueless

Version:

Vue Styleless UI Component Library, powered by Tailwind CSS.

146 lines (124 loc) 4.05 kB
import { merge } from "lodash-es"; import { defineConfig } from "cva"; import { extendTailwindMerge } from "tailwind-merge"; import { isCSR, isSSR } from "./helper.ts"; import { createGetMergedConfig } from "./node/mergeConfigs.js"; import { COMPONENT_NAME as U_ICON } from "../ui.image-icon/constants.ts"; import { getSelectedBrandColor } from "./theme.ts"; import { BRAND_COLOR, GRAYSCALE_COLOR, ICON_NON_PROPS_DEFAULTS, TAILWIND_MERGE_EXTENSION, } from "../constants.js"; import type { Config, Defaults, Strategies, BrandColors, UnknownObject, ComponentNames, } from "../types.ts"; interface MergedConfigOptions { defaultConfig: unknown; globalConfig: unknown; propsConfig?: unknown; vuelessStrategy?: Strategies; } type GetMergedConfig = (options: MergedConfigOptions) => unknown; /** * Load Vueless config from the project root. * Both for server and client side renderings. * IIFE for SSR is used to prevent top level await issue. */ export let vuelessConfig: Config = {}; if (isSSR) { /* Load Vueless config from the project root in IIFE (no top-level await). */ (async () => { try { const filePath = `${process.cwd()}/vueless.config`; vuelessConfig = (await import(/* @vite-ignore */ `${filePath}.js`)).default; if (!vuelessConfig) { vuelessConfig = (await import(/* @vite-ignore */ `${filePath}.ts`)).default; } } catch { vuelessConfig = {}; } })(); } if (isCSR) { vuelessConfig = Object.values( import.meta.glob(["/vueless.config.{js,ts}", "/**/vueless.config.{js,ts}"], { eager: true, import: "default", }), )[0] || {}; } /** * Extend twMerge (tailwind merge) by vueless and user config: */ const twMerge = extendTailwindMerge(merge(TAILWIND_MERGE_EXTENSION, vuelessConfig.tailwindMerge)); /** * Export cva (class variance authority) methods: * – extended with tailwind-merge * – remove all Vueless nested component names ({U...} strings) from class list string. * Learn more here: https://beta.cva.style */ export const { cx, compose, cva: classVarianceAuthority, } = defineConfig({ hooks: { onComplete: (classNames) => twMerge(classNames), }, }); export const getMergedConfig = createGetMergedConfig(cx) as GetMergedConfig; /* This allows skipping some CVA config keys in vueless config. */ export const cva = ({ base = "", variants = {}, compoundVariants = [], defaultVariants = {} }) => classVarianceAuthority({ base, variants, compoundVariants, defaultVariants, }); /** * Return default values for component props, icons, etc.. */ export function getDefaults<Props, Config>(defaultConfig: Config, name: ComponentNames) { const componentDefaults = (defaultConfig as UnknownObject).defaults || {}; const globalDefaults = vuelessConfig.components?.[name]?.defaults || {}; const defaults = merge({}, componentDefaults, globalDefaults) as Props & Defaults; if (defaults.color) { defaults.color = getColor(defaults.color as BrandColors); } /* Remove non a props defaults. */ for (const key in defaults) { const isNonPropIcon = /Icon/.test(key) && !/(leftIcon|rightIcon)/.test(key); const isNonPropIconDefaults = ICON_NON_PROPS_DEFAULTS.includes(key) && name === U_ICON; if (isNonPropIcon || isNonPropIconDefaults) { delete defaults[key]; } } return { ...defaults, dataTest: "", config: () => ({}), }; } /** * Return `grayscale` color if in component config it `brand` but in vueless config it `grayscale` * Otherwise return given color. */ export function getColor(color: string) { const isComponentColorBrand = color === BRAND_COLOR; const isSelectedColorGrayscale = getSelectedBrandColor() === GRAYSCALE_COLOR; return isComponentColorBrand && isSelectedColorGrayscale ? GRAYSCALE_COLOR : color; } /** * Replace in tailwind classes `{color}` variable into given color. */ export function setColor(classes: string, color: string) { return classes?.replace(/{color}/g, color); }