vueless
Version:
Vue Styleless UI Component Library, powered by Tailwind CSS.
148 lines (123 loc) • 3.98 kB
text/typescript
import { merge } from "lodash-es";
import { defineConfig } from "cva";
import { extendTailwindMerge } from "tailwind-merge";
import { isCSR, isSSR } from "./helper";
import { createGetMergedConfig } from "./node/mergeConfigs";
import { COMPONENT_NAME as U_ICON } from "../ui.image-icon/constants";
import { ICON_NON_PROPS_DEFAULTS, TAILWIND_MERGE_EXTENSION } from "../constants";
import type {
Config,
UnknownObject,
ComponentNames,
ComponentDefaults,
ComponentCustomProps,
} from "../types";
interface MergedConfigOptions {
defaultConfig: unknown;
globalConfig: unknown;
propsConfig?: unknown;
unstyled?: boolean;
}
type GetMergedConfig = (options: MergedConfigOptions) => unknown;
/**
* Load Vueless config from the project root.
* Both for server and client side renderings.
*/
export let vuelessConfig: Config = {};
export function setVuelessConfig(config?: Config) {
config = config || {};
vuelessConfig = Object.keys(config).length ? config : vuelessConfig;
}
if (isCSR) {
vuelessConfig =
Object.values(
import.meta.glob(["/vueless.config.{js,ts}"], {
eager: true,
import: "default",
}),
)[0] || {};
}
if (isSSR) {
(async () => {
try {
// @ts-expect-error: vueless.config.{js,ts} is optional
vuelessConfig = (await import(/* @vite-ignore */ "/vueless.config")).default;
} catch {
vuelessConfig = {};
}
})();
}
/**
* 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 Config & UnknownObject).defaults || {};
const globalDefaults = vuelessConfig.components?.[name]?.defaults || {};
const customProps = vuelessConfig.components?.[name]?.props as ComponentCustomProps;
const customPropsDefaults = getCustomPropsDefaults(customProps) || {};
const defaults = merge({}, componentDefaults, globalDefaults, customPropsDefaults) as Props &
ComponentDefaults;
/* Remove non a props defaults. */
for (const key in defaults) {
const isNonPropIcon =
/Icon/.test(key) && !/(leftIcon|rightIcon|toggleIcon|placeholderIcon)/.test(key);
const isNonPropIconDefaults = ICON_NON_PROPS_DEFAULTS.includes(key) && name === U_ICON;
if (isNonPropIcon || isNonPropIconDefaults) {
delete defaults[key];
}
}
return {
...defaults,
dataTest: "",
config: () => ({}),
};
}
/**
* Replace in tailwind classes `{color}` variable into given color.
*/
export function setColor(classes: string, color: string) {
return classes?.replace(/{color}/g, color);
}
/**
* Retrieves the default values from the provided component custom properties.
*/
export function getCustomPropsDefaults(props: ComponentCustomProps) {
const customPropsDefaults: ComponentDefaults = {};
if (!props) {
return customPropsDefaults;
}
for (const [key, value] of Object.entries(props)) {
if (value.default) {
customPropsDefaults[key] = value.default;
}
}
return customPropsDefaults;
}