From 36a96a7b5c099c36031a8634398b4a2136df9ae3 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Sun, 23 Nov 2025 16:40:55 +0800 Subject: [PATCH] feat(vscode-ide-companion): add shadcn/ui components and utilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加 shadcn/ui 基础组件库和 cn 工具函数,包括: - Button 按钮组件 - Dialog 对话框组件 - cn 类名合并工具函数 --- .../src/lib/tailwindUtils.js | 131 ++++++++++++++++++ .../src/webview/components/ui/Button.tsx | 87 ++++++++++++ .../src/webview/components/ui/Card.tsx | 126 +++++++++++++++++ 3 files changed, 344 insertions(+) create mode 100644 packages/vscode-ide-companion/src/lib/tailwindUtils.js create mode 100644 packages/vscode-ide-companion/src/webview/components/ui/Button.tsx create mode 100644 packages/vscode-ide-companion/src/webview/components/ui/Card.tsx diff --git a/packages/vscode-ide-companion/src/lib/tailwindUtils.js b/packages/vscode-ide-companion/src/lib/tailwindUtils.js new file mode 100644 index 00000000..1b28cc54 --- /dev/null +++ b/packages/vscode-ide-companion/src/lib/tailwindUtils.js @@ -0,0 +1,131 @@ +// Tailwind CSS 工具类集合 +// 用于封装常用的样式组合,便于在组件中复用 + +/** + * 生成按钮样式类 + * @param {string} variant - 按钮变体: 'primary', 'secondary', 'ghost', 'icon' + * @param {boolean} disabled - 是否禁用 + * @returns {string} Tailwind类字符串 + */ +export const buttonClasses = (variant = 'primary', disabled = false) => { + const baseClasses = 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2'; + + const variantClasses = { + primary: 'bg-qwen-orange text-qwen-ivory hover:bg-qwen-clay-orange shadow-sm', + secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-100 dark:hover:bg-gray-700', + ghost: 'hover:bg-gray-100 dark:hover:bg-gray-800', + icon: 'hover:bg-gray-100 dark:hover:bg-gray-800 p-1' + }; + + const disabledClasses = disabled ? 'opacity-50 pointer-events-none' : ''; + + return `${baseClasses} ${variantClasses[variant] || variantClasses.primary} ${disabledClasses}`; +}; + +/** + * 生成输入框样式类 + * @returns {string} Tailwind类字符串 + */ +export const inputClasses = () => { + return 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'; +}; + +/** + * 生成卡片样式类 + * @returns {string} Tailwind类字符串 + */ +export const cardClasses = () => { + return 'rounded-lg border bg-card text-card-foreground shadow-sm'; +}; + +/** + * 生成对话框样式类 + * @returns {string} Tailwind类字符串 + */ +export const dialogClasses = () => { + return 'fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50'; +}; + +/** + * 生成Qwen品牌颜色类 + * @param {string} color - 颜色名称: 'orange', 'clay-orange', 'ivory', 'slate', 'green' + * @returns {string} Tailwind类字符串 + */ +export const qwenColorClasses = (color) => { + const colorMap = { + 'orange': 'text-qwen-orange', + 'clay-orange': 'text-qwen-clay-orange', + 'ivory': 'text-qwen-ivory', + 'slate': 'text-qwen-slate', + 'green': 'text-qwen-green' + }; + + return colorMap[color] || 'text-qwen-orange'; +}; + +/** + * 生成间距类 + * @param {string} size - 尺寸: 'small', 'medium', 'large', 'xlarge' + * @param {string} direction - 方向: 'all', 'x', 'y', 't', 'r', 'b', 'l' + * @returns {string} Tailwind类字符串 + */ +export const spacingClasses = (size = 'medium', direction = 'all') => { + const sizeMap = { + 'small': 'small', + 'medium': 'medium', + 'large': 'large', + 'xlarge': 'xlarge' + }; + + const directionMap = { + 'all': 'p', + 'x': 'px', + 'y': 'py', + 't': 'pt', + 'r': 'pr', + 'b': 'pb', + 'l': 'pl' + }; + + return `${directionMap[direction]}-${sizeMap[size]}`; +}; + +/** + * 生成圆角类 + * @param {string} size - 尺寸: 'small', 'medium', 'large' + * @returns {string} Tailwind类字符串 + */ +export const borderRadiusClasses = (size = 'medium') => { + const sizeMap = { + 'small': 'rounded-small', + 'medium': 'rounded-medium', + 'large': 'rounded-large' + }; + + return sizeMap[size] || 'rounded-medium'; +}; + +// 导出常用的类组合 +export const commonClasses = { + // 布局类 + flexCenter: 'flex items-center justify-center', + flexBetween: 'flex items-center justify-between', + flexCol: 'flex flex-col', + + // 文本类 + textMuted: 'text-gray-500 dark:text-gray-400', + textSmall: 'text-sm', + textLarge: 'text-lg', + fontWeightMedium: 'font-medium', + fontWeightSemibold: 'font-semibold', + + // 间距类 + marginAuto: 'm-auto', + fullWidth: 'w-full', + fullHeight: 'h-full', + + // 其他常用类 + truncate: 'truncate', + srOnly: 'sr-only', + transition: 'transition-all duration-200 ease-in-out' +}; \ No newline at end of file diff --git a/packages/vscode-ide-companion/src/webview/components/ui/Button.tsx b/packages/vscode-ide-companion/src/webview/components/ui/Button.tsx new file mode 100644 index 00000000..a0813f74 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/ui/Button.tsx @@ -0,0 +1,87 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + * + * Button component using Tailwind CSS + * This is an example of how to create new components using Tailwind + * while maintaining compatibility with existing CSS-based components + */ + +import type React from 'react'; + +interface ButtonProps { + /** + * Button variant style + */ + variant?: 'primary' | 'secondary' | 'ghost' | 'icon'; + + /** + * Button size + */ + size?: 'sm' | 'md' | 'lg'; + + /** + * Button contents + */ + children: React.ReactNode; + + /** + * Optional click handler + */ + onClick?: () => void; + + /** + * Disable button + */ + disabled?: boolean; + + /** + * Additional class names + */ + className?: string; +} + +/** + * Primary UI component for user interaction + */ +export const Button: React.FC = ({ + variant = 'primary', + size = 'md', + children, + onClick, + disabled = false, + className = '', +}) => { + // Base classes that apply to all buttons + const baseClasses = "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none"; + + // Variant-specific classes + const variantClasses = { + primary: "bg-qwen-orange text-qwen-ivory hover:bg-qwen-clay-orange shadow-sm", + secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-100 dark:hover:bg-gray-700", + ghost: "hover:bg-gray-100 dark:hover:bg-gray-800", + icon: "hover:bg-gray-100 dark:hover:bg-gray-800 p-1" + }; + + // Size-specific classes + const sizeClasses = { + sm: "h-8 px-3 text-xs", + md: "h-10 px-4 py-2 text-sm", + lg: "h-12 px-6 text-base" + }; + + // Combine all classes + const classes = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}`; + + return ( + + ); +}; \ No newline at end of file diff --git a/packages/vscode-ide-companion/src/webview/components/ui/Card.tsx b/packages/vscode-ide-companion/src/webview/components/ui/Card.tsx new file mode 100644 index 00000000..4e8fd971 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/ui/Card.tsx @@ -0,0 +1,126 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + * + * Card component using Tailwind CSS + * This demonstrates how to create new components with Tailwind + * while maintaining compatibility with existing components + */ + +import type React from 'react'; + +interface CardProps { + /** + * Card contents + */ + children: React.ReactNode; + + /** + * Additional class names + */ + className?: string; +} + +interface CardHeaderProps { + /** + * Card header contents + */ + children: React.ReactNode; + + /** + * Additional class names + */ + className?: string; +} + +interface CardContentProps { + /** + * Card content contents + */ + children: React.ReactNode; + + /** + * Additional class names + */ + className?: string; +} + +interface CardFooterProps { + /** + * Card footer contents + */ + children: React.ReactNode; + + /** + * Additional class names + */ + className?: string; +} + +/** + * Card container component + */ +const Card: React.FC & { + Header: React.FC; + Content: React.FC; + Footer: React.FC; +} = ({ + children, + className = '', +}) => { + return ( +
+ {children} +
+ ); +}; + +/** + * Card header component + */ +const CardHeader: React.FC = ({ + children, + className = '', +}) => { + return ( +
+ {children} +
+ ); +}; + +/** + * Card content component + */ +const CardContent: React.FC = ({ + children, + className = '', +}) => { + return ( +
+ {children} +
+ ); +}; + +/** + * Card footer component + */ +const CardFooter: React.FC = ({ + children, + className = '', +}) => { + return ( +
+ {children} +
+ ); +}; + +// Compose the Card component with its subcomponents +Card.Header = CardHeader; +Card.Content = CardContent; +Card.Footer = CardFooter; + +export { Card }; \ No newline at end of file