diff --git a/.gitignore b/.gitignore index 068dc306..fb5041c7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* +.env node_modules .DS_Store dist diff --git a/apps/www/src/content/docs/installation.md b/apps/www/src/content/docs/installation.md index 754b57ab..44a20cd9 100644 --- a/apps/www/src/content/docs/installation.md +++ b/apps/www/src/content/docs/installation.md @@ -292,74 +292,41 @@ Add the following to your `src/app.postcss` file. You can learn more about using You'll want to create a `cn` helper to make it easier to conditionally add Tailwind CSS classes. Additionally, you'll want to add the custom transition that is used by various components. ```ts title="src/lib/utils.ts" +import type { Updater } from '@tanstack/vue-table' import { type ClassValue, clsx } from 'clsx' import { twMerge } from 'tailwind-merge' -import { cubicOut } from 'svelte/easing' -import type { TransitionConfig } from 'svelte/transition' +import { type Ref, camelize, getCurrentInstance, toHandlerKey } from 'vue' export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } -interface FlyAndScaleParams { - y?: number - x?: number - start?: number - duration?: number +// Vue doesn't have emits forwarding, in order to bind the emits we have to convert events into `onXXX` handlers +// issue: https://github.com/vuejs/core/issues/5917 +export function useEmitAsProps( + emit: (name: Name, ...args: any[]) => void, +) { + const vm = getCurrentInstance() + + const events = vm?.type.emits as Name[] + const result: Record = {} + if (!events?.length) { + console.warn( + `No emitted event found. Please check component: ${vm?.type.__name}`, + ) + } + + events?.forEach((ev) => { + result[toHandlerKey(camelize(ev))] = (...arg: any) => emit(ev, ...arg) + }) + return result } -export function flyAndScale(node: Element, - params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }): TransitionConfig { - const style = getComputedStyle(node) - const transform = style.transform === 'none' ? '' : style.transform - - const scaleConversion = ( - valueA: number, - scaleA: [number, number], - scaleB: [number, number] - ) => { - const [minA, maxA] = scaleA - const [minB, maxB] = scaleB - - const percentage = (valueA - minA) / (maxA - minA) - const valueB = percentage * (maxB - minB) + minB - - return valueB - } - - const styleToString = ( - style: Record - ): string => { - return Object.keys(style).reduce((str, key) => { - if (style[key] === undefined) - return str - return `${str + key}:${style[key]};` - }, '') - } - - return { - duration: params.duration ?? 200, - delay: 0, - css: (t) => { - const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]) - const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]) - const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]) - - return styleToString({ - transform: - `${transform - }translate3d(${ - x - }px, ${ - y - }px, 0) scale(${ - scale - })`, - opacity: t - }) - }, - easing: cubicOut - } +export function valueUpdater>(updaterOrValue: T, ref: Ref) { + ref.value + = typeof updaterOrValue === 'function' + ? updaterOrValue(ref.value) + : updaterOrValue } ``` diff --git a/apps/www/src/lib/utils.ts b/apps/www/src/lib/utils.ts index 726b53a0..adfdddf7 100644 --- a/apps/www/src/lib/utils.ts +++ b/apps/www/src/lib/utils.ts @@ -1,13 +1,7 @@ import type { Updater } from '@tanstack/vue-table' -import type { ClassValue } from 'clsx' -import { clsx } from 'clsx' +import { type ClassValue, clsx } from 'clsx' import { twMerge } from 'tailwind-merge' -import type { FunctionalComponent, Ref } from 'vue' -import { camelize, defineComponent, getCurrentInstance, h, toHandlerKey } from 'vue' - -export type ParseEmits> = { - [K in keyof T]: (...args: T[K]) => void; -} +import { type Ref, camelize, getCurrentInstance, toHandlerKey } from 'vue' export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) @@ -34,15 +28,19 @@ export function useEmitAsProps( return result } -export function convertToComponent(component: FunctionalComponent) { - return defineComponent({ - setup() { return () => h(component) }, - }) -} - export function valueUpdater>(updaterOrValue: T, ref: Ref) { ref.value = typeof updaterOrValue === 'function' ? updaterOrValue(ref.value) : updaterOrValue } + +// export type ParseEmits> = { +// [K in keyof T]: (...args: T[K]) => void; +// } + +// export function convertToComponent(component: FunctionalComponent) { +// return defineComponent({ +// setup() { return () => h(component) }, +// }) +// } diff --git a/packages/cli/package.json b/packages/cli/package.json index 18fb0aed..48b535d4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -72,6 +72,7 @@ "@types/lodash.template": "^4.5.1", "@types/prompts": "^2.4.4", "@vitest/ui": "^0.34.3", + "magic-string": "^0.30.3", "rimraf": "^5.0.1", "tsup": "^7.2.0", "type-fest": "^4.3.0", diff --git a/packages/cli/src/commands/add.ts b/packages/cli/src/commands/add.ts index e9dd5e93..eb5b6477 100644 --- a/packages/cli/src/commands/add.ts +++ b/packages/cli/src/commands/add.ts @@ -7,6 +7,7 @@ import { execa } from 'execa' import ora from 'ora' import prompts from 'prompts' import * as z from 'zod' +import { transformImport } from '../utils/transformers/transform-import' import { getConfig } from '@/src/utils/get-config' import { getPackageManager } from '@/src/utils/get-package-manager' import { handleError } from '@/src/utils/handle-error' @@ -18,7 +19,6 @@ import { getRegistryIndex, resolveTree, } from '@/src/utils/registry' -import { transform } from '@/src/utils/transformers' const addOptionsSchema = z.object({ components: z.array(z.string()).optional(), @@ -107,6 +107,7 @@ export const add = new Command() } const spinner = ora('Installing components...').start() + const skippedDeps = new Set() for (const item of payload) { spinner.text = `Installing ${item.name}...` const targetDir = await getItemTargetPath( @@ -122,41 +123,49 @@ export const add = new Command() await fs.mkdir(targetDir, { recursive: true }) const existingComponent = item.files.filter(file => - existsSync(path.resolve(targetDir, file.name)), + existsSync(path.resolve(targetDir, item.name, file.name)), ) if (existingComponent.length && !options.overwrite) { if (selectedComponents.includes(item.name)) { logger.warn( - `Component ${item.name} already exists. Use ${chalk.green( - '--overwrite', - )} to overwrite.`, + `\nComponent ${ + item.name + } already exists. Use ${chalk.green( + '--overwrite', + )} to overwrite.`, ) - process.exit(1) + spinner.stop() + process.exitCode = 1 + return } continue } for (const file of item.files) { - const filePath = path.resolve(targetDir, file.name) + const componentDir = path.resolve(targetDir, item.name) + const filePath = path.resolve( + targetDir, + item.name, + file.name, + ) // Run transformers. - const content = await transform({ - filename: file.name, - raw: file.content, - config, - baseColor, - }) + const content = transformImport(file.content, config) - // if (!config.tsx) - // filePath = filePath.replace(/\.tsx$/, '.jsx') + if (!existsSync(componentDir)) + await fs.mkdir(componentDir, { recursive: true }) await fs.writeFile(filePath, content) } // Install dependencies. if (item.dependencies?.length) { + item.dependencies.forEach(dep => + skippedDeps.add(dep), + ) + const packageManager = await getPackageManager(cwd) await execa( packageManager, diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index c8a96f1d..ceefcbe3 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -112,8 +112,8 @@ export async function promptForConfig( name: 'framework', message: `Which ${highlight('framework')} are you using?`, choices: [ - { title: 'Nuxt', value: 'nuxt' }, { title: 'Vite + Vue', value: 'vue' }, + { title: 'Nuxt', value: 'nuxt' }, ], }, { @@ -140,7 +140,7 @@ export async function promptForConfig( type: 'text', name: 'tailwindCss', message: `Where is your ${highlight('Tailwind CSS')} file?`, - initial: (prev, values) => defaultConfig?.tailwind.css ?? values.framework === 'nuxt' ? DEFAULT_TAILWIND_CSS_NUXT : DEFAULT_TAILWIND_CSS, + initial: (prev, values) => defaultConfig?.tailwind.css ?? (values.framework === 'nuxt' ? DEFAULT_TAILWIND_CSS_NUXT : DEFAULT_TAILWIND_CSS), }, { type: 'toggle', diff --git a/packages/cli/src/utils/get-config.ts b/packages/cli/src/utils/get-config.ts index 1b49b685..28d50add 100644 --- a/packages/cli/src/utils/get-config.ts +++ b/packages/cli/src/utils/get-config.ts @@ -6,8 +6,8 @@ import { resolveImport } from '@/src/utils/resolve-import' export const DEFAULT_STYLE = 'default' export const DEFAULT_COMPONENTS = '@/components' -export const DEFAULT_UTILS = '@/utils' -export const DEFAULT_TAILWIND_CSS = 'src/style.css' +export const DEFAULT_UTILS = '@/lib/utils' +export const DEFAULT_TAILWIND_CSS = 'src/assets/index.css' export const DEFAULT_TAILWIND_CSS_NUXT = 'assets/style/tailwind.css' export const DEFAULT_TAILWIND_CONFIG = 'tailwind.config.js' export const DEFAULT_TAILWIND_BASE_COLOR = 'slate' @@ -61,8 +61,17 @@ export async function getConfig(cwd: string) { } export async function resolveConfigPaths(cwd: string, config: RawConfig) { + const TSCONFIG_PATH = config.framework === 'nuxt' ? '.nuxt/tsconfig.json' : './tsconfig.json' + // In new Vue project, tsconfig has references to tsconfig.app.json, which is causing the path not resolving correctly + const FALLBACK_TSCONFIG_PATH = './tsconfig.app.json' + // Read tsconfig.json. - const tsConfig = await loadConfig(cwd) + const tsconfigPath = path.resolve(cwd, TSCONFIG_PATH) + let tsConfig = loadConfig(tsconfigPath) + + // If no paths were found, we load the fallback tsconfig + if ('paths' in tsConfig && Object.keys(tsConfig.paths).length === 0) + tsConfig = loadConfig(path.resolve(cwd, FALLBACK_TSCONFIG_PATH)) if (tsConfig.resultType === 'failed') { throw new Error( diff --git a/packages/cli/src/utils/registry/index.ts b/packages/cli/src/utils/registry/index.ts index 62ebb891..4b1e8638 100644 --- a/packages/cli/src/utils/registry/index.ts +++ b/packages/cli/src/utils/registry/index.ts @@ -144,7 +144,6 @@ async function fetchRegistry(paths: string[]) { return await response.json() }), ) - return results } catch (error) { diff --git a/packages/cli/src/utils/templates.ts b/packages/cli/src/utils/templates.ts index 657f86bd..1ef07f4c 100644 --- a/packages/cli/src/utils/templates.ts +++ b/packages/cli/src/utils/templates.ts @@ -1,10 +1,29 @@ -export const UTILS = `import { type ClassValue, clsx } from "clsx" -import { twMerge } from "tailwind-merge" +export const UTILS = `import { type ClassValue, clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' +import { camelize, getCurrentInstance, toHandlerKey } from 'vue' export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } -` + +export function useEmitAsProps( + emit: (name: Name, ...args: any[]) => void, +) { + const vm = getCurrentInstance() + + const events = vm?.type.emits as Name[] + const result: Record = {} + if (!events?.length) { + console.warn( + 'No emitted event found. Please check component: \${vm?.type.__name}', + ) + } + + events?.forEach((ev) => { + result[toHandlerKey(camelize(ev))] = (...arg: any) => emit(ev, ...arg) + }) + return result +}` export const UTILS_JS = `import { clsx } from "clsx" import { twMerge } from "tailwind-merge" @@ -12,6 +31,23 @@ import { twMerge } from "tailwind-merge" export function cn(...inputs) { return twMerge(clsx(inputs)) } + +export function useEmitAsProps(emit) { + const vm = getCurrentInstance() + + const events = vm?.type.emits + const result = {} + if (!events?.length) { + console.warn( + 'No emitted event found. Please check component: \${vm?.type.__name}', + ) + } + + events?.forEach((ev) => { + result[toHandlerKey(camelize(ev))] = (...arg) => emit(ev, ...arg) + }) + return result +} ` export const TAILWIND_CONFIG = `/** @type {import('tailwindcss').Config} */ diff --git a/packages/cli/src/utils/transformers/index.ts b/packages/cli/src/utils/transformers/index.ts index f7d271c7..35cc649e 100644 --- a/packages/cli/src/utils/transformers/index.ts +++ b/packages/cli/src/utils/transformers/index.ts @@ -6,7 +6,6 @@ import type * as z from 'zod' import type { Config } from '@/src/utils/get-config' import type { registryBaseColorSchema } from '@/src/utils/registry/schema' import { transformCssVars } from '@/src/utils/transformers/transform-css-vars' -import { transformImport } from '@/src/utils/transformers/transform-import' export interface TransformOpts { filename: string @@ -22,7 +21,6 @@ export type Transformer = ( ) => Promise const transformers: Transformer[] = [ - transformImport, transformCssVars, ] diff --git a/packages/cli/src/utils/transformers/transform-import.ts b/packages/cli/src/utils/transformers/transform-import.ts index ac8b78d3..57ad4aab 100644 --- a/packages/cli/src/utils/transformers/transform-import.ts +++ b/packages/cli/src/utils/transformers/transform-import.ts @@ -1,32 +1,49 @@ -import type { Transformer } from '@/src/utils/transformers' +import MagicString from 'magic-string' +import type { z } from 'zod' +import type { Config } from '../get-config' +import type { registryBaseColorSchema } from '../registry/schema' -export const transformImport: Transformer = async ({ sourceFile, config }) => { - const importDeclarations = sourceFile.getImportDeclarations() - - for (const importDeclaration of importDeclarations) { - const moduleSpecifier = importDeclaration.getModuleSpecifierValue() - - // Replace @/registry/[style] with the components alias. - if (moduleSpecifier.startsWith('@/registry/')) { - importDeclaration.setModuleSpecifier( - moduleSpecifier.replace( - /^@\/registry\/[^/]+/, - config.aliases.components, - ), - ) - } - - // Replace `import { cn } from "@/lib/utils"` - if (moduleSpecifier === '@/lib/utils') { - const namedImports = importDeclaration.getNamedImports() - const cnImport = namedImports.find(i => i.getName() === 'cn') - if (cnImport) { - importDeclaration.setModuleSpecifier( - moduleSpecifier.replace(/^@\/lib\/utils/, config.aliases.utils), - ) - } - } - } - - return sourceFile +export interface TransformOpts { + filename: string + raw: string + config: Config + baseColor?: z.infer } + +export function transformImport(content: string, config: Config) { + const s = new MagicString(content) + s.replaceAll(/@\/registry\/[^/]+/g, config.aliases.components) + s.replaceAll(/\$lib\/utils/g, config.aliases.utils) + return s.toString() +} + +// export const transformImport: Transformer = async ({ sourceFile, config }) => { +// const importDeclarations = sourceFile.getImportDeclarations() + +// for (const importDeclaration of importDeclarations) { +// const moduleSpecifier = importDeclaration.getModuleSpecifierValue() + +// // Replace @/registry/[style] with the components alias. +// if (moduleSpecifier.startsWith('@/registry/')) { +// importDeclaration.setModuleSpecifier( +// moduleSpecifier.replace( +// /^@\/registry\/[^/]+/, +// config.aliases.components, +// ), +// ) +// } + +// // Replace `import { cn } from "@/lib/utils"` +// if (moduleSpecifier === '@/lib/utils') { +// const namedImports = importDeclaration.getNamedImports() +// const cnImport = namedImports.find(i => i.getName() === 'cn') +// if (cnImport) { +// importDeclaration.setModuleSpecifier( +// moduleSpecifier.replace(/^@\/lib\/utils/, config.aliases.utils), +// ) +// } +// } +// } + +// return sourceFile +// } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90040424..ca8e7c0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -144,9 +144,6 @@ importers: vite: specifier: ^4.3.9 version: 4.3.9(@types/node@20.5.7) - vite-tsconfig-paths: - specifier: ^4.2.0 - version: 4.2.0(typescript@5.0.2)(vite@4.3.9) vue-tsc: specifier: ^1.4.2 version: 1.4.2(typescript@5.0.2) @@ -229,6 +226,9 @@ importers: '@vitest/ui': specifier: ^0.34.3 version: 0.34.3(vitest@0.34.3) + magic-string: + specifier: ^0.30.3 + version: 0.30.3 rimraf: specifier: ^5.0.1 version: 5.0.1 @@ -7224,19 +7224,6 @@ packages: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - /tsconfck@2.1.2(typescript@5.0.2): - resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==} - engines: {node: ^14.13.1 || ^16 || >=18} - hasBin: true - peerDependencies: - typescript: ^4.3.5 || ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - dependencies: - typescript: 5.0.2 - dev: true - /tsconfck@2.1.2(typescript@5.2.2): resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==} engines: {node: ^14.13.1 || ^16 || >=18} @@ -7586,23 +7573,6 @@ packages: - terser dev: true - /vite-tsconfig-paths@4.2.0(typescript@5.0.2)(vite@4.3.9): - resolution: {integrity: sha512-jGpus0eUy5qbbMVGiTxCL1iB9ZGN6Bd37VGLJU39kTDD6ZfULTTb1bcc5IeTWqWJKiWV5YihCaibeASPiGi8kw==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true - dependencies: - debug: 4.3.4 - globrex: 0.1.2 - tsconfck: 2.1.2(typescript@5.0.2) - vite: 4.3.9(@types/node@20.5.7) - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /vite-tsconfig-paths@4.2.0(typescript@5.2.2): resolution: {integrity: sha512-jGpus0eUy5qbbMVGiTxCL1iB9ZGN6Bd37VGLJU39kTDD6ZfULTTb1bcc5IeTWqWJKiWV5YihCaibeASPiGi8kw==} peerDependencies: