Merge pull request #664 from RooVetGit/cte/shadcn-ui-storybook

Add shadcn/ui + Storybook
This commit is contained in:
Chris Estreich
2025-01-30 22:23:00 -08:00
committed by GitHub
29 changed files with 12971 additions and 11024 deletions

View File

@@ -4,6 +4,9 @@
"recommendations": [ "recommendations": [
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"connor4312.esbuild-problem-matchers", "connor4312.esbuild-problem-matchers",
"ms-vscode.extension-test-runner" "ms-vscode.extension-test-runner",
"csstools.postcss",
"bradlc.vscode-tailwindcss",
"tobermory.es6-string-html"
] ]
} }

View File

@@ -255,9 +255,15 @@ Roo Code is available on:
```bash ```bash
code --install-extension bin/roo-code-4.0.0.vsix code --install-extension bin/roo-code-4.0.0.vsix
``` ```
5. **Debug**: 5. **Start the webview (Vite/React app with HMR)**:
```bash
npm run dev
```
6. **Debug**:
- Press `F5` (or **Run** → **Start Debugging**) in VSCode to open a new session with Roo Code loaded. - Press `F5` (or **Run** → **Start Debugging**) in VSCode to open a new session with Roo Code loaded.
Changes to the webview will appear immediately. Changes to the core extension will require a restart of the extension host.
We use [changesets](https://github.com/changesets/changesets) for versioning and publishing. Check our `CHANGELOG.md` for release notes. We use [changesets](https://github.com/changesets/changesets) for versioning and publishing. Check our `CHANGELOG.md` for release notes.
--- ---

View File

@@ -227,7 +227,7 @@
"lint": "eslint src --ext ts && npm run lint --prefix webview-ui", "lint": "eslint src --ext ts && npm run lint --prefix webview-ui",
"package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production", "package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production",
"pretest": "npm run compile-tests && npm run compile && npm run lint", "pretest": "npm run compile-tests && npm run compile && npm run lint",
"start:webview": "cd webview-ui && npm run start", "dev": "cd webview-ui && npm run dev",
"test": "jest && npm run test:webview", "test": "jest && npm run test:webview",
"test:webview": "cd webview-ui && npm run test", "test:webview": "cd webview-ui && npm run test",
"test:extension": "vscode-test", "test:extension": "vscode-test",

View File

@@ -52,6 +52,11 @@ const vscode = {
this.id = id this.id = id
} }
}, },
ExtensionMode: {
Production: 1,
Development: 2,
Test: 3,
},
} }
module.exports = vscode module.exports = vscode

View File

@@ -279,7 +279,11 @@ export class ClineProvider implements vscode.WebviewViewProvider {
enableScripts: true, enableScripts: true,
localResourceRoots: [this.context.extensionUri], localResourceRoots: [this.context.extensionUri],
} }
webviewView.webview.html = this.getHtmlContent(webviewView.webview)
webviewView.webview.html =
this.context.extensionMode === vscode.ExtensionMode.Development
? this.getHMRHtmlContent(webviewView.webview)
: this.getHtmlContent(webviewView.webview)
// Sets up an event listener to listen for messages passed from the webview view context // Sets up an event listener to listen for messages passed from the webview view context
// and executes code based on the message that is recieved // and executes code based on the message that is recieved
@@ -403,6 +407,62 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.view?.webview.postMessage(message) await this.view?.webview.postMessage(message)
} }
private getHMRHtmlContent(webview: vscode.Webview): string {
const nonce = getNonce()
const stylesUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "assets", "index.css"])
const codiconsUri = getUri(webview, this.context.extensionUri, [
"node_modules",
"@vscode",
"codicons",
"dist",
"codicon.css",
])
const file = "src/index.tsx"
const localPort = "5173"
const localServerUrl = `localhost:${localPort}`
const scriptUri = `http://${localServerUrl}/${file}`
const reactRefresh = /*html*/ `
<script nonce="${nonce}" type="module">
import RefreshRuntime from "http://localhost:${localPort}/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
`
const csp = [
"default-src 'none'",
`font-src ${webview.cspSource}`,
`style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`,
`img-src ${webview.cspSource} data:`,
`script-src 'unsafe-eval' https://* http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
`connect-src https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
]
return /*html*/ `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<meta http-equiv="Content-Security-Policy" content="${csp.join("; ")}">
<link rel="stylesheet" type="text/css" href="${stylesUri}">
<link href="${codiconsUri}" rel="stylesheet" />
<title>Roo Code</title>
</head>
<body>
<div id="root"></div>
${reactRefresh}
<script type="module" src="${scriptUri}"></script>
</body>
</html>
`
}
/** /**
* Defines and returns the HTML that should be rendered within the webview panel. * Defines and returns the HTML that should be rendered within the webview panel.
* *
@@ -558,7 +618,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
} }
} }
let currentConfigName = (await this.getGlobalState("currentApiConfigName")) as string const currentConfigName = (await this.getGlobalState("currentApiConfigName")) as string
if (currentConfigName) { if (currentConfigName) {
if (!(await this.configManager.hasConfig(currentConfigName))) { if (!(await this.configManager.hasConfig(currentConfigName))) {
@@ -1134,7 +1194,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
if (message.text && message.apiConfiguration) { if (message.text && message.apiConfiguration) {
try { try {
await this.configManager.saveConfig(message.text, message.apiConfiguration) await this.configManager.saveConfig(message.text, message.apiConfiguration)
let listApiConfig = await this.configManager.listConfig() const listApiConfig = await this.configManager.listConfig()
await Promise.all([ await Promise.all([
this.updateGlobalState("listApiConfigMeta", listApiConfig), this.updateGlobalState("listApiConfigMeta", listApiConfig),
@@ -1159,7 +1219,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.configManager.saveConfig(newName, message.apiConfiguration) await this.configManager.saveConfig(newName, message.apiConfiguration)
await this.configManager.deleteConfig(oldName) await this.configManager.deleteConfig(oldName)
let listApiConfig = await this.configManager.listConfig() const listApiConfig = await this.configManager.listConfig()
const config = listApiConfig?.find((c) => c.name === newName) const config = listApiConfig?.find((c) => c.name === newName)
// Update listApiConfigMeta first to ensure UI has latest data // Update listApiConfigMeta first to ensure UI has latest data
@@ -1217,7 +1277,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.updateGlobalState("listApiConfigMeta", listApiConfig) await this.updateGlobalState("listApiConfigMeta", listApiConfig)
// If this was the current config, switch to first available // If this was the current config, switch to first available
let currentApiConfigName = await this.getGlobalState("currentApiConfigName") const currentApiConfigName = await this.getGlobalState("currentApiConfigName")
if (message.text === currentApiConfigName && listApiConfig?.[0]?.name) { if (message.text === currentApiConfigName && listApiConfig?.[0]?.name) {
const apiConfig = await this.configManager.loadConfig(listApiConfig[0].name) const apiConfig = await this.configManager.loadConfig(listApiConfig[0].name)
await Promise.all([ await Promise.all([
@@ -1237,7 +1297,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
break break
case "getListApiConfiguration": case "getListApiConfiguration":
try { try {
let listApiConfig = await this.configManager.listConfig() const listApiConfig = await this.configManager.listConfig()
await this.updateGlobalState("listApiConfigMeta", listApiConfig) await this.updateGlobalState("listApiConfigMeta", listApiConfig)
this.postMessageToWebview({ type: "listApiConfig", listApiConfig }) this.postMessageToWebview({ type: "listApiConfig", listApiConfig })
} catch (error) { } catch (error) {
@@ -1277,7 +1337,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.outputChannel.appendLine( this.outputChannel.appendLine(
`Failed to update timeout for ${message.serverName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, `Failed to update timeout for ${message.serverName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
) )
vscode.window.showErrorMessage(`Failed to update server timeout`) vscode.window.showErrorMessage("Failed to update server timeout")
} }
} }
break break
@@ -1630,7 +1690,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
async refreshGlamaModels() { async refreshGlamaModels() {
const glamaModelsFilePath = path.join(await this.ensureCacheDirectoryExists(), GlobalFileNames.glamaModels) const glamaModelsFilePath = path.join(await this.ensureCacheDirectoryExists(), GlobalFileNames.glamaModels)
let models: Record<string, ModelInfo> = {} const models: Record<string, ModelInfo> = {}
try { try {
const response = await axios.get("https://glama.ai/api/gateway/v1/models") const response = await axios.get("https://glama.ai/api/gateway/v1/models")
/* /*
@@ -1720,7 +1780,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
GlobalFileNames.openRouterModels, GlobalFileNames.openRouterModels,
) )
let models: Record<string, ModelInfo> = {} const models: Record<string, ModelInfo> = {}
try { try {
const response = await axios.get("https://openrouter.ai/api/v1/models") const response = await axios.get("https://openrouter.ai/api/v1/models")
/* /*

View File

@@ -108,6 +108,11 @@ jest.mock("vscode", () => ({
uriScheme: "vscode", uriScheme: "vscode",
language: "en", language: "en",
}, },
ExtensionMode: {
Production: 1,
Development: 2,
Test: 3,
},
})) }))
// Mock sound utility // Mock sound utility

View File

@@ -21,3 +21,5 @@
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
*storybook.log

View File

@@ -0,0 +1,16 @@
import type { StorybookConfig } from "@storybook/react-vite"
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
addons: [
"@storybook/addon-onboarding",
"@storybook/addon-essentials",
"@chromatic-com/storybook",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/react-vite",
options: {},
},
}
export default config

View File

@@ -0,0 +1,17 @@
import type { Preview } from "@storybook/react"
import "../src/index.css"
import "./vscode.css"
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
}
export default preview

View File

@@ -0,0 +1,32 @@
/**
* Use `Developer: Generate Color Theme From Current Settings` to generate themes
* using your current VSCode settings.
*
* See: https://code.visualstudio.com/docs/getstarted/themes
*/
:root {
--vscode-editor-background: #1f1f1f; /* "editor.background" */
--vscode-editor-foreground: #cccccc; /* "editor.foreground" */
--vscode-menu-background: #1f1f1f; /* "menu.background" */
--vscode-menu-foreground: #cccccc; /* "menu.foreground" */
--vscode-button-background: #0078d4; /* "button.background" */
--vscode-button-foreground: #ffffff; /* "button.foreground" */
--vscode-button-secondaryBackground: #313131; /* "button.secondaryBackground" */
--vscode-button-secondaryForeground: #cccccc; /* "button.secondaryForeground" */
--vscode-disabledForeground: red; /* "disabledForeground" */
--vscode-descriptionForeground: #9d9d9d; /* "descriptionForeground" */
--vscode-focusBorder: #0078d4; /* "focusBorder" */
--vscode-errorForeground: #f85149; /* "errorForeground" */
--vscode-widget-border: #313131; /* "widget.border" */
--vscode-input-background: #313131; /* "input.background" */
--vscode-input-foreground: #cccccc; /* "input.foreground" */
--vscode-input-border: #3c3c3c; /* "input.border" */
/* I can't find these in the output of `Developer: Generate Color Theme From Current Settings` */
--vscode-charts-red: red;
--vscode-charts-blue: blue;
--vscode-charts-yellow: yellow;
--vscode-charts-orange: orange;
--vscode-charts-green: green;
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,14 +4,20 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc -b && vite build",
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint src --ext ts,tsx",
"test": "jest", "test": "jest",
"lint": "eslint src --ext ts,tsx" "storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-slot": "^1.1.1",
"@tailwindcss/vite": "^4.0.0",
"@vscode/webview-ui-toolkit": "^1.4.0", "@vscode/webview-ui-toolkit": "^1.4.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"debounce": "^2.1.1", "debounce": "^2.1.1",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"fzf": "^0.5.2", "fzf": "^0.5.2",
@@ -24,36 +30,45 @@
"rehype-highlight": "^7.0.0", "rehype-highlight": "^7.0.0",
"shell-quote": "^1.8.2", "shell-quote": "^1.8.2",
"styled-components": "^6.1.13", "styled-components": "^6.1.13",
"tailwind-merge": "^2.6.0",
"tailwindcss": "^4.0.0", "tailwindcss": "^4.0.0",
"vscrui": "^0.2.0", "tailwindcss-animate": "^1.0.7",
"@tailwindcss/vite": "^4.0.0" "vscrui": "^0.2.0"
}, },
"devDependencies": { "devDependencies": {
"@chromatic-com/storybook": "^3.2.4",
"@storybook/addon-essentials": "^8.5.2",
"@storybook/addon-interactions": "^8.5.2",
"@storybook/addon-onboarding": "^8.5.2",
"@storybook/blocks": "^8.5.2",
"@storybook/react": "^8.5.2",
"@storybook/react-vite": "^8.5.2",
"@storybook/test": "^8.5.2",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2", "@types/jest": "^27.5.2",
"@types/node": "^18.0.0", "@types/node": "^18.0.0",
"@types/react": "^18.3.3", "@types/react": "^18.3.18",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.5",
"@types/shell-quote": "^1.7.5", "@types/shell-quote": "^1.7.5",
"@types/testing-library__jest-dom": "^5.14.5", "@types/testing-library__jest-dom": "^5.14.5",
"@types/vscode-webview": "^1.57.5", "@types/vscode-webview": "^1.57.5",
"@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0", "@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-storybook": "^0.11.2",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^27.5.1", "jest": "^27.5.1",
"jest-environment-jsdom": "^27.5.1", "jest-environment-jsdom": "^27.5.1",
"jest-simple-dot-reporter": "^1.0.5", "jest-simple-dot-reporter": "^1.0.5",
"postcss": "^8.5.1", "storybook": "^8.5.2",
"ts-jest": "^27.1.5", "ts-jest": "^27.1.5",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"vite": "^5.4.14" "vite": "6.0.11"
}, },
"jest": { "jest": {
"testEnvironment": "jsdom", "testEnvironment": "jsdom",
@@ -93,20 +108,5 @@
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}", "<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}" "<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
] ]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all",
"last 2 chrome version",
"last 2 firefox version",
"last 2 safari version"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
} }
} }

View File

View File

@@ -1,38 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
--></body>
</html>

View File

@@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -183,26 +183,28 @@ export const ChatRowContent = ({
</div> </div>
) )
return [ return [
apiReqCancelReason !== null ? ( apiReqCancelReason ? (
apiReqCancelReason === "user_cancelled" ? ( apiReqCancelReason === "user_cancelled" ? (
getIconSpan("error", cancelledColor) getIconSpan("error", cancelledColor)
) : ( ) : (
getIconSpan("error", errorColor) getIconSpan("error", errorColor)
) )
) : cost !== null ? ( ) : cost ? (
getIconSpan("check", successColor) getIconSpan("check", successColor)
) : apiRequestFailedMessage ? ( ) : apiRequestFailedMessage ? (
getIconSpan("error", errorColor) getIconSpan("error", errorColor)
) : ( ) : (
<ProgressIndicator /> <ProgressIndicator />
), ),
apiReqCancelReason !== null ? ( apiReqCancelReason ? (
apiReqCancelReason === "user_cancelled" ? ( apiReqCancelReason === "user_cancelled" ? (
<span style={{ color: normalColor, fontWeight: "bold" }}>API Request Cancelled</span> <span style={{ color: normalColor, fontWeight: "bold" }}>API Request Cancelled</span>
) : ( ) : (
<span style={{ color: errorColor, fontWeight: "bold" }}>API Streaming Failed</span> <span style={{ color: errorColor, fontWeight: "bold" }}>
API Streaming Failed ({JSON.stringify(apiReqCancelReason)})
</span>
) )
) : cost !== null ? ( ) : cost ? (
<span style={{ color: normalColor, fontWeight: "bold" }}>API Request</span> <span style={{ color: normalColor, fontWeight: "bold" }}>API Request</span>
) : apiRequestFailedMessage ? ( ) : apiRequestFailedMessage ? (
<span style={{ color: errorColor, fontWeight: "bold" }}>API Request Failed</span> <span style={{ color: errorColor, fontWeight: "bold" }}>API Request Failed</span>
@@ -510,9 +512,7 @@ export const ChatRowContent = ({
style={{ style={{
...headerStyle, ...headerStyle,
marginBottom: marginBottom:
(cost === null && apiRequestFailedMessage) || apiReqStreamingFailedMessage (!cost && apiRequestFailedMessage) || apiReqStreamingFailedMessage ? 10 : 0,
? 10
: 0,
justifyContent: "space-between", justifyContent: "space-between",
cursor: "pointer", cursor: "pointer",
userSelect: "none", userSelect: "none",
@@ -530,7 +530,7 @@ export const ChatRowContent = ({
</div> </div>
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span> <span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
</div> </div>
{((cost === null && apiRequestFailedMessage) || apiReqStreamingFailedMessage) && ( {((!cost && apiRequestFailedMessage) || apiReqStreamingFailedMessage) && (
<> <>
<p style={{ ...pStyle, color: "var(--vscode-errorForeground)" }}> <p style={{ ...pStyle, color: "var(--vscode-errorForeground)" }}>
{apiRequestFailedMessage || apiReqStreamingFailedMessage} {apiRequestFailedMessage || apiReqStreamingFailedMessage}

View File

@@ -0,0 +1,47 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline: "border border-input bg-foreground shadow-sm hover:bg-foreground/80",
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
},
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -1,44 +1,71 @@
@import "tailwindcss"; /* @import "tailwindcss"; */
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
/* https://tailwindcss.com/docs/preflight */
/* @import "tailwindcss/preflight.css" layer(base); */
@import "tailwindcss/utilities.css" layer(utilities);
@plugin "tailwindcss-animate";
@theme {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);
}
@layer base { @layer base {
/* Theme Variables - VSCode Integration */
:root { :root {
/* Base Colors */
--background: var(--vscode-editor-background); --background: var(--vscode-editor-background);
--foreground: var(--vscode-editor-foreground); --foreground: var(--vscode-editor-foreground);
/* Component Colors */
--card: var(--vscode-editor-background); --card: var(--vscode-editor-background);
--card-foreground: var(--vscode-editor-foreground); --card-foreground: var(--vscode-editor-foreground);
--popover: var(--vscode-menu-background, var(--vscode-editor-background)); --popover: var(--vscode-menu-background, var(--vscode-editor-background));
--popover-foreground: var(--vscode-menu-foreground, var(--vscode-editor-foreground)); --popover-foreground: var(--vscode-menu-foreground, var(--vscode-editor-foreground));
/* Button Colors */
--primary: var(--vscode-button-background); --primary: var(--vscode-button-background);
--primary-foreground: var(--vscode-button-foreground); --primary-foreground: var(--vscode-button-foreground);
--secondary: var(--vscode-button-secondaryBackground); --secondary: var(--vscode-button-secondaryBackground);
--secondary-foreground: var(--vscode-button-secondaryForeground); --secondary-foreground: var(--vscode-button-secondaryForeground);
--accent: var(--vscode-focusBorder);
--accent-foreground: var(--vscode-button-foreground);
/* State Colors */
--muted: var(--vscode-disabledForeground); --muted: var(--vscode-disabledForeground);
--muted-foreground: var(--vscode-descriptionForeground); --muted-foreground: var(--vscode-descriptionForeground);
--accent: var(--vscode-input-border);
--accent-foreground: var(--vscode-button-foreground);
--destructive: var(--vscode-errorForeground); --destructive: var(--vscode-errorForeground);
--destructive-foreground: var(--vscode-editor-background); --destructive-foreground: var(--vscode-button-foreground);
/* UI Elements */
--border: var(--vscode-widget-border); --border: var(--vscode-widget-border);
--input: var(--vscode-input-background); --input: var(--vscode-input-background);
--ring: var(--vscode-focusBorder); --ring: var(--vscode-input-border);
--radius: 0.5rem;
/* Chart Colors - Using VSCode's chart colors */
--chart-1: var(--vscode-charts-red); --chart-1: var(--vscode-charts-red);
--chart-2: var(--vscode-charts-blue); --chart-2: var(--vscode-charts-blue);
--chart-3: var(--vscode-charts-yellow); --chart-3: var(--vscode-charts-yellow);
--chart-4: var(--vscode-charts-orange); --chart-4: var(--vscode-charts-orange);
--chart-5: var(--vscode-charts-green); --chart-5: var(--vscode-charts-green);
--radius: 0.5rem;
} }
} }
@@ -70,10 +97,10 @@ vscode-button::part(control):focus {
outline: none; outline: none;
} }
/* /**
Use vscode native scrollbar styles * Use vscode native scrollbar styles
https://github.com/gitkraken/vscode-gitlens/blob/b1d71d4844523e8b2ef16f9e007068e91f46fd88/src/webviews/apps/home/home.scss * https://github.com/gitkraken/vscode-gitlens/blob/b1d71d4844523e8b2ef16f9e007068e91f46fd88/src/webviews/apps/home/home.scss
*/ */
html { html {
height: 100%; height: 100%;
@@ -163,10 +190,11 @@ The above scrollbar styling uses some transparent background color magic to acco
background-color: transparent; background-color: transparent;
} }
/* /**
Dropdown label * Dropdown label
https://github.com/microsoft/vscode-webview-ui-toolkit/tree/main/src/dropdown#with-label * https://github.com/microsoft/vscode-webview-ui-toolkit/tree/main/src/dropdown#with-label
*/ */
.dropdown-container { .dropdown-container {
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
@@ -174,6 +202,7 @@ https://github.com/microsoft/vscode-webview-ui-toolkit/tree/main/src/dropdown#wi
align-items: flex-start; align-items: flex-start;
justify-content: flex-start; justify-content: flex-start;
} }
.dropdown-container label { .dropdown-container label {
display: block; display: block;
color: var(--vscode-foreground); color: var(--vscode-foreground);
@@ -184,6 +213,7 @@ https://github.com/microsoft/vscode-webview-ui-toolkit/tree/main/src/dropdown#wi
} }
/* Fix dropdown double scrollbar overflow */ /* Fix dropdown double scrollbar overflow */
#api-provider > div > ul { #api-provider > div > ul {
overflow: unset; overflow: unset;
} }
@@ -197,18 +227,20 @@ vscode-dropdown::part(listbox) {
} }
/* Faded icon buttons in textfields */ /* Faded icon buttons in textfields */
.input-icon-button { .input-icon-button {
cursor: pointer; cursor: pointer;
opacity: 0.65; opacity: 0.65;
} }
.input-icon-button:hover { .input-icon-button:hover {
opacity: 1; opacity: 1;
} }
.input-icon-button.disabled { .input-icon-button.disabled {
cursor: not-allowed; cursor: not-allowed;
opacity: 0.4; opacity: 0.4;
} }
.input-icon-button.disabled:hover { .input-icon-button.disabled:hover {
opacity: 0.4; opacity: 0.4;
} }
@@ -220,10 +252,6 @@ vscode-dropdown::part(listbox) {
border-radius: 3px; border-radius: 3px;
box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent); box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
color: transparent; color: transparent;
/* padding: 0.5px;
margin: -0.5px;
position: relative;
bottom: -0.5px; */
} }
.mention-context-highlight { .mention-context-highlight {

View File

@@ -1,12 +1,12 @@
import React from "react" import { StrictMode } from "react"
import ReactDOM from "react-dom/client" import { createRoot } from "react-dom/client"
import "./index.css" import "./index.css"
import App from "./App" import App from "./App"
import "../../node_modules/@vscode/codicons/dist/codicon.css" import "../../node_modules/@vscode/codicons/dist/codicon.css"
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement) createRoot(document.getElementById("root")!).render(
root.render( <StrictMode>
<React.StrictMode>
<App /> <App />
</React.StrictMode>, </StrictMode>,
) )

View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@@ -0,0 +1,53 @@
import type { Meta, StoryObj } from "@storybook/react"
import { fn } from "@storybook/test"
import { Button } from "@/components/ui/button"
const meta = {
title: "Example/Button",
component: Button,
parameters: { layout: "centered" },
tags: ["autodocs"],
argTypes: {},
args: { onClick: fn(), children: "Button" },
} satisfies Meta<typeof Button>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
variant: "default",
},
}
export const Secondary: Story = {
args: {
variant: "secondary",
},
}
export const Outline: Story = {
args: {
variant: "outline",
},
}
export const Ghost: Story = {
args: {
variant: "ghost",
},
}
export const Link: Story = {
args: {
variant: "link",
},
}
export const Destructive: Story = {
args: {
variant: "destructive",
},
}

View File

@@ -0,0 +1,7 @@
import { Meta } from "@storybook/blocks";
<Meta title="Welcome" />
# Welcome
This Roo Code storybook is used to independently develop components for the Roo Code webview UI.

View File

1
webview-ui/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -14,7 +14,11 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}, },
"include": ["src", "../src/shared"] "include": ["src", "../src/shared"]
} }

View File

@@ -1,23 +0,0 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [
react(),
tailwindcss(),
],
build: {
outDir: "build",
rollupOptions: {
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`,
},
},
},
server: {
port: 3000,
},
});

36
webview-ui/vite.config.ts Normal file
View File

@@ -0,0 +1,36 @@
import path from "path"
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import tailwindcss from "@tailwindcss/vite"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
build: {
outDir: "build",
rollupOptions: {
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`,
},
},
},
server: {
hmr: {
host: "localhost",
protocol: "ws",
},
cors: {
origin: "*",
methods: "*",
allowedHeaders: "*",
},
},
})