├── .eslintrc.cjs ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.default.md ├── README.md ├── bin └── test-pr.sh ├── components.json ├── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── favicon.ico ├── src ├── artifacts │ ├── index.tsx │ └── signup.tsx ├── components │ ├── layout.tsx │ └── ui │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── chart.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── context-menu.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── hover-card.tsx │ │ ├── input-otp.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── menubar.tsx │ │ ├── navigation-menu.tsx │ │ ├── pagination.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── resizable.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── sonner.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ ├── tooltip.tsx │ │ └── use-toast.ts ├── index.css ├── lib │ └── utils.ts ├── main.tsx ├── styled-jsx.d.ts └── vite-env.d.ts ├── tailwind.config.mjs ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | '@typescript-eslint/no-unused-vars': ['error', { 18 | argsIgnorePattern: '^_', 19 | varsIgnorePattern: '^_', 20 | }], 21 | }, 22 | overrides: [ 23 | { 24 | files: ['**/components/ui/**/*.tsx'], 25 | rules: { 26 | 'react-refresh/only-export-components': 'off' 27 | } 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [18.x, 20.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: 'npm' 25 | 26 | - name: Install dependencies 27 | run: npm ci 28 | 29 | - name: Run linting 30 | run: npm run lint 31 | 32 | - name: Build 33 | run: npm run build 34 | 35 | - name: Upload build artifacts 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: dist 39 | path: dist/ 40 | if-no-files-found: error -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Cláudio Silva 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.default.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Claude Artifact Runner 2 | 3 | A template project for easily converting Claude AI’s Artifacts into React applications, ready to run out of the box or extend as needed. 4 | 5 | ## TL/DR 6 | 7 | 1. You created a fancy web app using Claude's Artifacts feature. 8 | 2. You want to run it outside of Claude's website, or use it as a base for a larger project. 9 | 3. Clone, install and run this project. 10 | 4. Save your artifact(s) to the correct folder. 11 | 5. Done! 12 | 6. Optional step: continue developing your project into a full-fledged web application. 13 | 7. Optional step: generate a release build and publish your finished application to a web server or cloud service. 14 | 15 | ## Use Cases 16 | 17 | 1. Run Artifacts on your local machine, on a web server or on a cloud service. 18 | 2. Use Artifacts as a starting point for a new project and then extend it with custom code. 19 | 3. Create a new web application from scratch and add some Artifacts to it. 20 | 4. Or forget about Claude's Artifacts and just use the project as a starting point for your manually coded web application. 21 | 22 | ## Table of Contents 23 | 24 | - [Why is this needed?](#why-is-this-needed) 25 | - [What this project is not](#what-this-project-is-not) 26 | - [What this project actually is](#what-this-project-actually-is) 27 | - [Limitations](#limitations) 28 | - [What's included?](#whats-included) 29 | - [Prerequisites](#prerequisites) 30 | - [Getting started](#getting-started) 31 | - [Running a single Artifact](#running-a-single-artifact) 32 | - [Creating a multi-page application](#creating-a-multi-page-application) 33 | - [Developing a more complex application](#developing-a-more-complex-application) 34 | - [Project structure](#project-structure) 35 | - [Building for production](#building-for-production) 36 | - [Deploying your application](#deploying-your-application) 37 | - [Local test deployment](#local-test-deployment) 38 | - [Traditional web hosting](#traditional-web-hosting) 39 | - [Cloud hosting platforms](#cloud-hosting-platforms) 40 | - [Netlify](#netlify) 41 | - [Vercel](#vercel) 42 | - [GitHub Pages](#github-pages) 43 | - [Cloudflare Pages](#cloudflare-pages) 44 | - [Troubleshooting](#troubleshooting) 45 | - [Contributing](#contributing) 46 | - [License](#license) 47 | - [Acknowledgements](#acknowledgements) 48 | 49 | ## Why is this needed? 50 | 51 | After all, doesn't Claude already provide both Copy and Download buttons for you to get the generated code? 52 | 53 | Well, if you've created a small web application or component and want to use it outside of Claude's website, you'll be disappointed to find out that Claude will only provide you with a single file containing the main logic of the app, not the full project with all necessary files to run it independently. 54 | 55 | > Note that the code loaded by your web browser to run the Artifact is not the same as the code you copy or download from the interface; it is a transpiled, minimized and bundled version that includes all the necessary libraries (e.g., React) required to run it. 56 | 57 | If you're unfamiliar with the technologies used on the project, you'll have a hard time assembling and configuring all the required libraries and tooling required to make a running standalone app. 58 | 59 | Even if you're an experienced developer, you may just want to save time and effort and get the Artifacts running as easily and as soon as possible. 60 | 61 | This template project provides the fastest and easiest way to get your Artifacts up and running on your machine. It includes all the necessary dependencies and configurations to seamlessly transition your Claude-generated Artifacts into a fully functional web application in no time. 62 | 63 | ## What this project is not 64 | 65 | **This is not an Artifact viewer.** 66 | 67 | "What??" - you might say. 68 | 69 | Well, you **can** use it to view Artifacts, but it's not really intended for that. 70 | 71 | If this was just a viewer, it would most probably just display a simple interface with a text box and a panel, where you could paste the Artifact code. Then it would compile it at runtime and display it in the panel. 72 | 73 | But that would not be very useful, would it? 74 | 75 | ## What this project actually is 76 | 77 | This project converts Artifacts into actual, production-ready standalone web applications. 78 | 79 | This means it has no UI of its own. Your Artifacts will be the application's UI. 80 | 81 | You can also blend AI-generated Artifacts with your own custom code, incorporate code generated by other AI tools, such as **v0.dev**, or even build applications without Artifacts at all. 82 | 83 | Built with modern web development essentials including TypeScript, Tailwind CSS, Shadcn UI, and file-based routing, this pre-configured setup lets you focus on development rather than configuration. As a standard React application, you have the freedom to extend it with any features you need, from backend services like Supabase to web frameworks such as Express or Fastify. 84 | 85 | You'll be able to deploy your app anywhere, whether locally for your own use, in a company intranet or in a public-facing production environment, at the webhosting or cloud provider of your choice. 86 | 87 | ## Limitations 88 | 89 | This project is meant for running Artifacts that are interactive web apps, usually made in React, and for which Claude writes Javascript or Typescript code. 90 | 91 | **Mermaid diagrams, SVGs, and other document-type Artifacts are out of the project’s scope.** 92 | 93 | Also, Claude's Artifacts run client-side only (i.e. in the browser). As such, they are limited in their capabilities. 94 | 95 | If you need a full-stack application (with database, APIs, etc.), I'll be honest, this is not the best project for that, as it does not provide a backend. 96 | 97 | Nevertheless, it does provide a good starting point. You can still add a server-side framework to create a full-stack application, or use a cloud Backend service like Supabase or Firebase. 98 | 99 | ## What's included? 100 | 101 | These are the libraries and frameworks this project provides, identical* to those available on Claude's Artifacts environment: 102 | 103 | 1. **React 18** for building user interfaces. 104 | 2. **TypeScript** to support Artifacts written in type-safe Javascript. 105 | 3. **Vite** for fast development and building. 106 | 4. **Shadcn UI** for pre-built, customizable components. 107 | 5. **Tailwind CSS** for compact and expressive embedded styling. 108 | 6. **Recharts** for creating dynamic, customizable charts and data visualizations. 109 | 7. **Lucide React** for a comprehensive library of open-source icons designed for React applications. 110 | 111 | > \* Note that the actual versions of the packages currently in use in the Artifacts environment may differ from the ones installed by this project, as Anthropic may update them from time to time. 112 | > If a component generated by Claude fails to run properly because of an outdated package, please let me know. 113 | 114 | ## Prerequisites 115 | 116 | Before you begin, ensure you have the following installed: 117 | - Node.js 118 | minimum supported version is 16 (lts/gallium), tested up to version 23.2, version 22.11 is recommended 119 | - npm (usually comes with Node.js) 120 | 121 | ## Getting started 122 | 123 | 1. Clone the repository: 124 | ``` 125 | git clone https://github.com/claudio-silva/claude-artifact-runner.git 126 | cd claude-artifact-runner 127 | ``` 128 | 129 | 2. Install dependencies: 130 | ``` 131 | npm install 132 | ``` 133 | 134 | 3. Start the development server: 135 | ``` 136 | npm run dev 137 | ``` 138 | 139 | 4. Open your browser and visit `http://localhost:5173` to see the default app running. 140 | 141 | The default app is composed of two demo components: a login form and a signup form. You can navigate between them by clicking on the link at the bottom of the form. 142 | 143 | > These demo pages/components are just for demonstration purposes and can be easily replaced with your own components, either generated by Claude or created by yourself. 144 | > **This will NOT be the UI of your application.** 145 | 146 | ## Running a single Artifact 147 | 148 | If you just want to run a single Artifact, you can follow these simple steps: 149 | 150 | 1. Follow the "Getting started" steps. Leave the browser open at the initial page. 151 | 2. Delete the files in the `src/artifacts/` directory. 152 | 3. Download your Artifact from Claude.ai 153 | 4. Move the file to the `src/artifacts/` directory and rename it to `index.tsx`. 154 | 5. You'll immediately see your Artifact running on the open browser tab. 155 | 156 | Note that you'll be viewing the app in development mode. To generate the final app, ready for production, you'll need to build it first. See the instructions further below. 157 | 158 | ## Creating a multi-page application 159 | 160 | If you want to create a multi-page application, you can follow these steps: 161 | 162 | 1. Follow the previous section's steps. 163 | 2. Generate more Artifacts using Claude and download them to the `src/artifacts/` directory. 164 | 5. Give each file it a unique name, such as `your-component.tsx`. Each component you add will be a new page accessible at `http://localhost:5173/your-component-name` (without the `.tsx` extension). 165 | 6. If you have `npm run dev` running in the background, the new pages will be ready to display immediately. If a page is already open when being updated, it will refresh automatically. 166 | 7. You can link Artifacts to each other to build a multi-page application. 167 | > **It's easy:** on each Artifact page, just add links that navigate to the other Artifacts, by specifying their names without the `.tsx` extension. 168 | E.g. `Go to My Component` 169 | 8. You can also use the `useNavigate` hook to navigate to a specific page. 170 | 9. Finally, to create a release build and publish your finished application, follow the instructions further below. 171 | 172 | ## Developing a more complex application 173 | 174 | If you intend to create a more advanced application, you'll probably want to customize its visual appearance by changing styles, adding images and new components, creating a base layout with a top header and a sidebar with the main navigation menu, etc. 175 | 176 | This section is intended for advanced users only, and is not required for simple applications. 177 | 178 | ### Customization 179 | 180 | - For styling: 181 | - Prefer using Tailwind classes directly in components. 182 | - Use `src/index.css` only for Tailwind configuration and critical global styles. 183 | - For component-specific styles, use CSS Modules (*.module.css). 184 | - For complex styling needs (such as dynamic styles), consider styled-components or other CSS-in-JS solutions. 185 | - Modify `tailwind.config.mjs` to customize the Tailwind CSS theme. 186 | - Place static assets (such as images) in the `public` folder. 187 | - Update `main.tsx` to change the overall layout of the app (e.g. adding a navigation bar). 188 | - Add or modify components in the `src/components/` directory. 189 | > **Note:** Shadcn UI components installed via `npx` are automatically placed in `src/components/ui`. All components come pre-installed by default, but if you remove some and later want to reinstall any, you may simply run `npx shadcn-ui@latest add `. 190 | 191 | ### Removing unneeded components / libraries 192 | 193 | The **Recharts** library and ALL **Shadcn UI** components come pre-installed, so that all code that Claude may generate will run *out-of-the-box*. 194 | 195 | If you just want to run the Artifact locally, you may leave things as they are, but if you want to deploy the application or use it as a base for a larger project, you may want to optimize the application's bundle size. 196 | 197 | To do that, you may remove the pre-installed components or libraries that are not required by your application. 198 | 199 | #### Unneeded Shadcn UI components: 200 | Just delete the component's files from `src/components/ui`. 201 | 202 | #### Unneeded packages (ex: Recharts): 203 | Use `npm remove` to uninstall them. 204 | 205 | ## Project structure 206 | 207 | | Directory/File | Description | 208 | |--------------------------------------------|-------------------------------------------------------| 209 | | `dist/` | Compiled output | 210 | | `public/` | Standalone static assets | 211 | | `src/` | Contains the source code for the application | 212 | | `src/artifacts/` | **The Artifacts generated by Claude should be placed here** | 213 | | `src/assets/` | Static assets for the build system | 214 | | `src/components/` | Bundled Shadcn UI components | 215 | | `src/lib/utils.ts` | Utility functions and helpers | 216 | | `src/index.css` | Tailwind styles | 217 | | `src/main.tsx` | Entry point of the application | 218 | | `src/vite-env.d.ts` | Type definitions for Vite | 219 | | `.eslintrc.cjs` | ESLint configuration | 220 | | `components.json` | Shadcn UI components configuration | 221 | | `index.html` | Entry HTML file | 222 | | `jsconfig.json` | JSON configuration | 223 | | `postcss.config.js` | PostCSS configuration | 224 | | `tailwind.config.mjs` | Tailwind CSS configuration | 225 | | `tsconfig.app.json`, `tsconfig.json`, `tsconfig.node.json` | TypeScript configuration | 226 | | `package.json` | All the required packages are registered here | 227 | | `vite.config.ts` | Vite configuration | 228 | 229 | ## Building for production 230 | 231 | To create a production build, run: 232 | 233 | ``` 234 | npm run build 235 | ``` 236 | 237 | This will generate optimized files in the `dist/` directory, ready for deployment. 238 | 239 | ## Deploying your application 240 | 241 | After running `npm run build`, you'll have a `dist` folder containing the built files (typically an HTML file, a JavaScript file, and a CSS file). 242 | 243 | Here are several ways to deploy these files: 244 | 245 | ### Local test deployment 246 | 247 | For local testing of the production build, you can use the `serve` package: 248 | 249 | 1. Install `serve` globally: 250 | ``` 251 | npm install -g serve 252 | ``` 253 | 254 | 2. Navigate to your project directory and run: 255 | ``` 256 | serve -s dist 257 | ``` 258 | 259 | 3. Open a browser and go to `http://localhost:3000` (or the URL provided in the terminal). 260 | 261 | ### Traditional web hosting 262 | 263 | If you want to deploy to a shared or dedicated web server: 264 | 265 | 1. Upload the contents of the `dist` folder to your web server's public HTML directory (often called `public_html`, `www`, or `htdocs`). 266 | 267 | Remember to update any necessary configuration files (like `vite.config.ts`) before building your app if it is not being served from the root of your domain. 268 | 269 | For example, for `vite.config.ts`, you may configure it like this: 270 | ```javascript 271 | export default { 272 | base: '/subdirectory/', // Set this to the path your app is served from 273 | // other configurations 274 | }; 275 | ``` 276 | 277 | ### Cloud hosting platforms 278 | 279 | Here are some popular free cloud hosting platforms and how to deploy your app to them: 280 | 281 | > Remember to run `npm run build` before deploying to ensure you're uploading the latest version of your app. 282 | 283 | #### Netlify 284 | 285 | 1. Install the Netlify CLI: 286 | ``` 287 | npm install -g netlify-cli 288 | ``` 289 | 290 | 2. Run the following command in your project directory: 291 | ``` 292 | netlify deploy 293 | ``` 294 | 295 | 3. Follow the prompts. When asked for the publish directory, enter `dist`. 296 | 297 | 4. For production deployment, use: 298 | ``` 299 | netlify deploy --prod 300 | ``` 301 | 302 | #### Vercel 303 | 304 | 1. Install the Vercel CLI: 305 | ``` 306 | npm install -g vercel 307 | ``` 308 | 309 | 2. Run the following command in your project directory: 310 | ``` 311 | vercel 312 | ``` 313 | 314 | 3. Follow the prompts. Vercel will automatically detect that it's a Vite project and use the correct settings. 315 | 316 | #### GitHub Pages 317 | 318 | 1. If you haven't already, create a GitHub repository for your project. 319 | 320 | 2. Install the `gh-pages` package: 321 | ``` 322 | npm install gh-pages --save-dev 323 | ``` 324 | 325 | 3. Add these scripts to your `package.json`: 326 | ```json 327 | "scripts": { 328 | "predeploy": "npm run build", 329 | "deploy": "gh-pages -d dist" 330 | } 331 | ``` 332 | 333 | 4. Run: 334 | ``` 335 | npm run deploy 336 | ``` 337 | 338 | 5. Set up GitHub Pages in your repository settings to use the `gh-pages` branch. 339 | 340 | #### Cloudflare Pages 341 | 342 | You can deploy to Cloudflare Pages either through the Cloudflare dashboard or using the `wrangler` CLI tool. Here's how to do it using `wrangler`, which is often the most straightforward method: 343 | 344 | 1. **Install Wrangler:** 345 | ``` 346 | npm install -g wrangler 347 | ``` 348 | 349 | 2. **Login to Cloudflare:** 350 | ``` 351 | wrangler login 352 | ``` 353 | 354 | 3. **Deploy your project:** 355 | ``` 356 | wrangler pages deploy dist 357 | ``` 358 | 359 | This command will prompt you to create a new project if one doesn't exist, and then deploy your `dist` folder to Cloudflare Pages. 360 | 361 | 4. **Configure your project (optional):** 362 | If you need more control over your deployment, you can create a `wrangler.toml` file in your project root: 363 | 364 | ```toml 365 | name = "my-react-app" 366 | compatibility_date = "2024-07-16" # Replace with the current date 367 | 368 | [site] 369 | bucket = "./dist" 370 | ``` 371 | 372 | Note: The `account_id` and `workers_dev` fields are typically not needed for Cloudflare Pages deployments. 373 | 374 | 5. **Custom domain and production settings:** 375 | To use a custom domain or configure production settings, you can use the Cloudflare Pages dashboard. There, you can set up your domain, configure environment variables, and manage other deployment settings. 376 | 377 | ## Troubleshooting 378 | 379 | If you encounter any issues, try the following: 380 | 381 | 1. Clear your browser cache and restart the development server. 382 | 2. Delete the `node_modules` folder and run `npm install` again. 383 | 3. Make sure your Node.js version is compatible with the project requirements. 384 | 4. Check for any error messages in the console and search for solutions online. 385 | 386 | If problems persist, please open an issue on this project's GitHub repository. 387 | 388 | ## Contributing 389 | 390 | Contributions are welcome! Please feel free to submit a Pull Request. 391 | 392 | ## License 393 | 394 | This project is open source and available under the [MIT License](LICENSE). 395 | 396 | ## Acknowledgements 397 | 398 | I found [Claude-React-Jumpstart](https://github.com/Bklieger/Claude-React-Jumpstart) when looking for a way to run Artifacts outside of [claude.ai](https://claude.ai). 399 | 400 | However, it did not fully meet my needs, so I decided to make my own project, as I wanted something that: 401 | * was completely pre-configured (no need to install or configure additional stuff), 402 | * was ready to go with a single `npm install`, and 403 | * included all components and libraries needed to fully replicate the Artifacts environment. 404 | 405 | I would also like to thank [IntranetFactory](https://github.com/IntranetFactory) for contributing the routing solution for handling multiple Artifacts. 406 | -------------------------------------------------------------------------------- /bin/test-pr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit immediately if a command exits with a non-zero status. 4 | set -e 5 | 6 | # Colors for output 7 | RED='\033[0;31m' 8 | GREEN='\033[0;32m' 9 | YELLOW='\033[1;33m' 10 | BLUE='\033[0;34m' 11 | NC='\033[0m' # No Color 12 | 13 | # --- Helper Functions --- 14 | 15 | print_status() { 16 | echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" 17 | } 18 | 19 | print_success() { 20 | echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" 21 | } 22 | 23 | print_error() { 24 | echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" 25 | } 26 | 27 | print_info() { 28 | echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" 29 | } 30 | 31 | cleanup() { 32 | print_status "Running cleanup..." 33 | # Ensure we are back on the main branch if possible 34 | current_branch=$(git rev-parse --abbrev-ref HEAD || echo "unknown") 35 | if [[ "$current_branch" != "main" && "$current_branch" != "unknown" ]]; then 36 | print_status "Switching back to main branch..." 37 | git checkout main || print_error "Failed to checkout main branch during cleanup." 38 | fi 39 | 40 | # Restore package files if backups exist 41 | if [[ -f package.json.backup && -f package-lock.json.backup ]]; then 42 | print_status "Restoring package files..." 43 | mv package.json.backup package.json 44 | mv package-lock.json.backup package-lock.json 45 | else 46 | print_status "No backup files found to restore." 47 | fi 48 | print_status "Cleanup finished." 49 | } 50 | 51 | # Trap EXIT signal to ensure cleanup runs 52 | trap cleanup EXIT 53 | 54 | run_single_test() { 55 | local pr_number=$1 56 | local test_passed=true 57 | 58 | print_info "--- Testing PR #$pr_number ---" 59 | 60 | print_status "Checking out PR #$pr_number..." 61 | if ! gh pr checkout $pr_number; then 62 | print_error "Failed to checkout PR #$pr_number. Skipping." 63 | git checkout main # Attempt to return to main 64 | return 1 65 | fi 66 | 67 | print_status "Installing dependencies for PR #$pr_number..." 68 | if ! npm ci; then # Use npm ci for cleaner installs 69 | print_error "npm ci failed for PR #$pr_number." 70 | test_passed=false 71 | fi 72 | 73 | if $test_passed; then 74 | print_status "Running linting for PR #$pr_number..." 75 | if ! npm run lint; then 76 | print_error "Linting failed for PR #$pr_number." 77 | test_passed=false 78 | fi 79 | fi 80 | 81 | if $test_passed; then 82 | print_status "Running build for PR #$pr_number..." 83 | if ! npm run build; then 84 | print_error "Build failed for PR #$pr_number." 85 | test_passed=false 86 | fi 87 | fi 88 | 89 | if $test_passed; then 90 | print_success "PR #$pr_number passed tests!" 91 | else 92 | print_error "PR #$pr_number failed tests." 93 | fi 94 | 95 | print_status "Returning to main branch..." 96 | git checkout main 97 | 98 | # Decide return status (0 for success, 1 for failure) 99 | if $test_passed; then 100 | return 0 101 | else 102 | return 1 103 | fi 104 | } 105 | 106 | # --- Main Execution --- 107 | 108 | # Check prerequisites 109 | if ! command -v gh &> /dev/null; then 110 | print_error "GitHub CLI ('gh') could not be found. Please install it." 111 | exit 1 112 | fi 113 | 114 | if ! gh auth status &> /dev/null; then 115 | print_error "Not logged into GitHub CLI ('gh auth status' failed). Please log in." 116 | exit 1 117 | fi 118 | 119 | if [ ! -f "package.json" ]; then 120 | print_error "Script must be run from the project root directory (where package.json exists)." 121 | exit 1 122 | fi 123 | 124 | print_status "Starting PR tester..." 125 | 126 | # Stash any local changes to prevent conflicts 127 | print_status "Stashing any local changes..." 128 | if ! git stash push -m "Temporary stash for PR testing" > /dev/null; then 129 | print_error "Failed to stash local changes. Please commit or stash your changes manually before running this script." 130 | exit 1 131 | fi 132 | 133 | # Ask user what type of PRs to test 134 | print_info "What type of PRs would you like to test?" 135 | echo "1) All open PRs" 136 | echo "2) Only Dependabot PRs" 137 | read -p "Enter your choice (1 or 2): " pr_type_choice 138 | 139 | # Set the author filter based on user choice 140 | author_filter="" 141 | if [ "$pr_type_choice" = "2" ]; then 142 | author_filter="--author app/dependabot" 143 | print_status "Testing only Dependabot PRs..." 144 | else 145 | print_status "Testing all open PRs..." 146 | fi 147 | 148 | # Create backups *once* 149 | print_status "Creating backups of package files..." 150 | cp package.json package.json.backup 151 | cp package-lock.json package-lock.json.backup 152 | 153 | # Fetch open PRs based on user choice 154 | print_status "Fetching open PRs..." 155 | # Get PRs into an array using standard array assignment 156 | pr_options=() 157 | while IFS= read -r line; do 158 | pr_options+=("$line") 159 | done < <(gh pr list $author_filter --state open --json number,title --jq '.[] | "#\(.number): \(.title)"') 160 | 161 | if [ ${#pr_options[@]} -eq 0 ]; then 162 | print_error "No open PRs found." 163 | # Restore stashed changes 164 | git stash pop > /dev/null 165 | # Cleanup will run automatically on exit 166 | exit 1 167 | fi 168 | 169 | # Store the number of PRs for later use 170 | num_prs=${#pr_options[@]} 171 | 172 | # Add options for Test All and Quit 173 | pr_options+=("Test All" "Quit") 174 | 175 | # Present the menu 176 | print_info "Please select a PR to test:" 177 | PS3="Enter your choice: " 178 | select opt in "${pr_options[@]}"; do 179 | case $opt in 180 | "Test All") 181 | print_status "Selected: Test All" 182 | all_passed=true 183 | # Extract numbers using standard array assignment 184 | pr_numbers=() 185 | while IFS= read -r number; do 186 | pr_numbers+=("$number") 187 | done < <(gh pr list $author_filter --state open --json number --jq '.[].number') 188 | 189 | for pr_num in "${pr_numbers[@]}"; do 190 | if ! run_single_test $pr_num; then 191 | all_passed=false 192 | print_error "Testing stopped due to failure in PR #$pr_num. You may need manual intervention." 193 | # You might want to change this to 'continue' if you want to test remaining PRs even if one fails 194 | break 195 | fi 196 | done 197 | if $all_passed; then 198 | print_success "All tested PRs passed!" 199 | else 200 | print_error "One or more PRs failed tests." 201 | # Restore stashed changes before exiting 202 | git stash pop > /dev/null 203 | exit 1 # Exit with error if any PR failed 204 | fi 205 | break 206 | ;; 207 | "Quit") 208 | print_status "Exiting." 209 | break 210 | ;; 211 | *) 212 | # Check if the choice is a valid PR option 213 | if [[ "$REPLY" =~ ^[0-9]+$ ]] && [ "$REPLY" -le "$num_prs" ]; then 214 | # Extract PR number from the selected option (e.g., "#123: Title") 215 | pr_string=${pr_options[$(($REPLY-1))]} 216 | pr_number=$(echo "$pr_string" | sed -n 's/^#\([0-9]*\):.*/\1/p') 217 | print_status "Selected: Test PR #$pr_number" 218 | if run_single_test $pr_number; then 219 | print_success "Testing for PR #$pr_number completed successfully." 220 | else 221 | print_error "Testing for PR #$pr_number failed." 222 | # Restore stashed changes before exiting 223 | git stash pop > /dev/null 224 | exit 1 # Exit with error if the selected PR failed 225 | fi 226 | break 227 | else 228 | print_error "Invalid option. Please try again." 229 | fi 230 | ;; 231 | esac 232 | done 233 | 234 | # Restore stashed changes 235 | print_status "Restoring local changes..." 236 | git stash pop > /dev/null 237 | 238 | # Cleanup will run automatically via trap EXIT 239 | print_status "Script finished." 240 | exit 0 # Explicitly exit with success if we reached here -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "src/components", 15 | "utils": "src/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Artifact Viewer 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | }, 8 | "include": ["src/**/*"] 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "artifact-viewer", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@babel/runtime": "^7.27.0", 14 | "@hookform/resolvers": "^3.9.0", 15 | "@radix-ui/react-accordion": "^1.2.0", 16 | "@radix-ui/react-alert-dialog": "^1.1.1", 17 | "@radix-ui/react-aspect-ratio": "^1.1.0", 18 | "@radix-ui/react-avatar": "^1.1.0", 19 | "@radix-ui/react-checkbox": "^1.1.1", 20 | "@radix-ui/react-collapsible": "^1.1.0", 21 | "@radix-ui/react-context-menu": "^2.2.1", 22 | "@radix-ui/react-dialog": "^1.1.1", 23 | "@radix-ui/react-dropdown-menu": "^2.1.1", 24 | "@radix-ui/react-hover-card": "^1.1.1", 25 | "@radix-ui/react-label": "^2.1.0", 26 | "@radix-ui/react-menubar": "^1.1.1", 27 | "@radix-ui/react-navigation-menu": "^1.2.0", 28 | "@radix-ui/react-popover": "^1.1.1", 29 | "@radix-ui/react-progress": "^1.1.0", 30 | "@radix-ui/react-radio-group": "^1.2.0", 31 | "@radix-ui/react-scroll-area": "^1.1.0", 32 | "@radix-ui/react-select": "^2.1.1", 33 | "@radix-ui/react-separator": "^1.1.0", 34 | "@radix-ui/react-slider": "^1.2.0", 35 | "@radix-ui/react-slot": "^1.1.0", 36 | "@radix-ui/react-switch": "^1.1.0", 37 | "@radix-ui/react-tabs": "^1.1.0", 38 | "@radix-ui/react-toast": "^1.2.1", 39 | "@radix-ui/react-toggle": "^1.1.0", 40 | "@radix-ui/react-toggle-group": "^1.1.0", 41 | "@radix-ui/react-tooltip": "^1.1.2", 42 | "class-variance-authority": "^0.7.0", 43 | "clsx": "^2.1.1", 44 | "cmdk": "^1.0.0", 45 | "date-fns": "^3.6.0", 46 | "embla-carousel-react": "^8.1.6", 47 | "input-otp": "^1.2.4", 48 | "lucide-react": "^0.408.0", 49 | "next-themes": "^0.3.0", 50 | "react": "^18.3.1", 51 | "react-day-picker": "^8.10.1", 52 | "react-dom": "^18.3.1", 53 | "react-hook-form": "^7.52.1", 54 | "react-resizable-panels": "^2.0.20", 55 | "react-router-dom": "^7.1.2", 56 | "recharts": "^2.15.0", 57 | "sonner": "^1.5.0", 58 | "tailwind-merge": "^2.4.0", 59 | "tailwindcss-animate": "^1.0.7", 60 | "vaul": "^0.9.1", 61 | "zod": "^3.23.8" 62 | }, 63 | "devDependencies": { 64 | "@types/node": "^20.14.10", 65 | "@types/react": "^18.3.3", 66 | "@types/react-dom": "^18.3.0", 67 | "@typescript-eslint/eslint-plugin": "^7.13.1", 68 | "@typescript-eslint/parser": "^7.13.1", 69 | "@vitejs/plugin-react": "^4.3.1", 70 | "autoprefixer": "^10.4.19", 71 | "eslint": "^8.57.0", 72 | "eslint-plugin-react-hooks": "^4.6.2", 73 | "eslint-plugin-react-refresh": "^0.4.7", 74 | "glob": "^10.4.5", 75 | "postcss": "^8.4.39", 76 | "rimraf": "^5.0.5", 77 | "tailwindcss": "^3.4.4", 78 | "typescript": "~5.5.0", 79 | "vite": "^5.4.17", 80 | "vite-plugin-pages": "^0.32.4" 81 | }, 82 | "overrides": { 83 | "glob": "^10.4.5", 84 | "rimraf": "^5.0.5", 85 | "@humanwhocodes/config-array": "npm:@eslint/config-array@latest", 86 | "@humanwhocodes/object-schema": "npm:@eslint/object-schema@latest", 87 | "inflight": "^2.0.1" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAGXRFWHRTb2Z0d2FyZQBNYWNpbnRvc2ggSEQgdjEuOTAg4Baq2gAAAD9JREFUKFNjZGBgYGBgUggUGBoa/N8BgwERnLx9uZkBlJDAyMjKAkYEHETVzkKACGcEAMON0IxtZG6XAAAAAElFTkSuQmCC 2 | -------------------------------------------------------------------------------- /src/artifacts/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { AlertCircle, Mail, Lock, Github, Facebook, Twitter } from 'lucide-react'; 3 | import { Alert, AlertDescription } from '@/components/ui/alert'; 4 | import { Button } from '@/components/ui/button'; 5 | import { Input } from '@/components/ui/input'; 6 | import { Label } from '@/components/ui/label'; 7 | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; 8 | import { Link } from "react-router-dom"; 9 | 10 | const LoginForm = () => { 11 | const [email, setEmail] = useState(''); 12 | const [password, setPassword] = useState(''); 13 | const [error, setError] = useState(''); 14 | 15 | const handleLogin = (e: React.FormEvent) => { 16 | e.preventDefault(); 17 | if (!email || !password) { 18 | setError('Please fill in all fields'); 19 | } else { 20 | setError(''); 21 | console.log('Login attempted:', { email, password }); 22 | // Here you would typically handle the login logic 23 | alert(`Login attempted: ${email}, ${password}`); 24 | } 25 | }; 26 | 27 | const handleSocialLogin = (platform: string) => { 28 | console.log(`${platform} login attempted`); 29 | // Here you would typically handle the social login logic 30 | alert(`${platform} login attempted`); 31 | }; 32 | 33 | return ( 34 |
35 | 36 | 37 | Demo Login Component 38 | 39 | 40 |
41 |
42 | 43 |
44 | 45 | setEmail(e.target.value)} 51 | className="pl-10" 52 | /> 53 |
54 |
55 |
56 | 57 |
58 | 59 | setPassword(e.target.value)} 65 | className="pl-10" 66 | /> 67 |
68 |
69 | 70 |
71 | 72 | {error && ( 73 | 74 | 75 | {error} 76 | 77 | )} 78 | 79 |
80 |
81 | 82 |
83 |
84 | Or continue with 85 |
86 |
87 | 88 |
89 | 93 | 97 | 101 |
102 | 103 |
104 | Don't have an account?{' '} 105 | 106 | Sign up 107 | 108 |
109 |
110 |
111 |
112 | ); 113 | }; 114 | 115 | export default LoginForm; -------------------------------------------------------------------------------- /src/artifacts/signup.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { AlertCircle, Mail, Lock, Github, Facebook, Twitter } from 'lucide-react'; 3 | import { Alert, AlertDescription } from '@/components/ui/alert'; 4 | import { Button } from '@/components/ui/button'; 5 | import { Input } from '@/components/ui/input'; 6 | import { Label } from '@/components/ui/label'; 7 | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; 8 | import { Link } from "react-router-dom"; 9 | 10 | const SignupForm = () => { 11 | const [email, setEmail] = useState(''); 12 | const [password, setPassword] = useState(''); 13 | const [confirmPassword, setConfirmPassword] = useState(''); 14 | const [error, setError] = useState(''); 15 | 16 | const handleSignup = (e: React.FormEvent) => { 17 | e.preventDefault(); 18 | if (!email || !password || !confirmPassword) { 19 | setError('Please fill in all fields'); 20 | } else if (password !== confirmPassword) { 21 | setError('Passwords do not match'); 22 | } else { 23 | setError(''); 24 | console.log('Signup attempted:', { email, password }); 25 | // Here you would typically handle the signup logic 26 | alert(`Signup attempted: ${email}, ${password}`); 27 | } 28 | }; 29 | 30 | const handleSocialLogin = (platform: string) => { 31 | console.log(`${platform} signup attempted`); 32 | // Here you would typically handle the social signup logic 33 | alert(`${platform} signup attempted`); 34 | }; 35 | 36 | return ( 37 |
38 | 39 | 40 | Create an Account 41 | 42 | 43 |
44 |
45 | 46 |
47 | 48 | setEmail(e.target.value)} 54 | className="pl-10" 55 | /> 56 |
57 |
58 |
59 | 60 |
61 | 62 | setPassword(e.target.value)} 68 | className="pl-10" 69 | /> 70 |
71 |
72 |
73 | 74 |
75 | 76 | setConfirmPassword(e.target.value)} 82 | className="pl-10" 83 | /> 84 |
85 |
86 | 87 |
88 | 89 | {error && ( 90 | 91 | 92 | {error} 93 | 94 | )} 95 | 96 |
97 |
98 | 99 |
100 |
101 | Or continue with 102 |
103 |
104 | 105 |
106 | 110 | 114 | 118 |
119 | 120 |
121 | Already have an account?{' '} 122 | 123 | Log in 124 | 125 |
126 |
127 |
128 |
129 | ); 130 | }; 131 | 132 | export default SignupForm; 133 | -------------------------------------------------------------------------------- /src/components/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return ( 5 | <> 6 | {children} 7 | 8 | ); 9 | } -------------------------------------------------------------------------------- /src/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 3 | import { ChevronDown } from "lucide-react" 4 | 5 | import { cn } from "src/lib/utils" 6 | 7 | const Accordion = AccordionPrimitive.Root 8 | 9 | const AccordionItem = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 18 | )) 19 | AccordionItem.displayName = "AccordionItem" 20 | 21 | const AccordionTrigger = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, children, ...props }, ref) => ( 25 | 26 | svg]:rotate-180", 30 | className 31 | )} 32 | {...props} 33 | > 34 | {children} 35 | 36 | 37 | 38 | )) 39 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 40 | 41 | const AccordionContent = React.forwardRef< 42 | React.ElementRef, 43 | React.ComponentPropsWithoutRef 44 | >(({ className, children, ...props }, ref) => ( 45 | 50 |
{children}
51 |
52 | )) 53 | 54 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 55 | 56 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 57 | -------------------------------------------------------------------------------- /src/components/ui/alert-dialog.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" 3 | 4 | import { cn } from "@/lib/utils" 5 | import { buttonVariants } from "@/components/ui/button" 6 | 7 | const AlertDialog = AlertDialogPrimitive.Root 8 | 9 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger 10 | 11 | const AlertDialogPortal = AlertDialogPrimitive.Portal 12 | 13 | const AlertDialogOverlay = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef 16 | >(({ className, ...props }, ref) => ( 17 | 25 | )) 26 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName 27 | 28 | const AlertDialogContent = React.forwardRef< 29 | React.ElementRef, 30 | React.ComponentPropsWithoutRef 31 | >(({ className, ...props }, ref) => ( 32 | 33 | 34 | 42 | 43 | )) 44 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName 45 | 46 | const AlertDialogHeader = ({ 47 | className, 48 | ...props 49 | }: React.HTMLAttributes) => ( 50 |
57 | ) 58 | AlertDialogHeader.displayName = "AlertDialogHeader" 59 | 60 | const AlertDialogFooter = ({ 61 | className, 62 | ...props 63 | }: React.HTMLAttributes) => ( 64 |
71 | ) 72 | AlertDialogFooter.displayName = "AlertDialogFooter" 73 | 74 | const AlertDialogTitle = React.forwardRef< 75 | React.ElementRef, 76 | React.ComponentPropsWithoutRef 77 | >(({ className, ...props }, ref) => ( 78 | 83 | )) 84 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName 85 | 86 | const AlertDialogDescription = React.forwardRef< 87 | React.ElementRef, 88 | React.ComponentPropsWithoutRef 89 | >(({ className, ...props }, ref) => ( 90 | 95 | )) 96 | AlertDialogDescription.displayName = 97 | AlertDialogPrimitive.Description.displayName 98 | 99 | const AlertDialogAction = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )) 109 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName 110 | 111 | const AlertDialogCancel = React.forwardRef< 112 | React.ElementRef, 113 | React.ComponentPropsWithoutRef 114 | >(({ className, ...props }, ref) => ( 115 | 124 | )) 125 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName 126 | 127 | export { 128 | AlertDialog, 129 | AlertDialogPortal, 130 | AlertDialogOverlay, 131 | AlertDialogTrigger, 132 | AlertDialogContent, 133 | AlertDialogHeader, 134 | AlertDialogFooter, 135 | AlertDialogTitle, 136 | AlertDialogDescription, 137 | AlertDialogAction, 138 | AlertDialogCancel, 139 | } 140 | -------------------------------------------------------------------------------- /src/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "src/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | destructive: 13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | } 20 | ) 21 | 22 | const Alert = React.forwardRef< 23 | HTMLDivElement, 24 | React.HTMLAttributes & VariantProps 25 | >(({ className, variant, ...props }, ref) => ( 26 |
32 | )) 33 | Alert.displayName = "Alert" 34 | 35 | const AlertTitle = React.forwardRef< 36 | HTMLParagraphElement, 37 | React.HTMLAttributes 38 | >(({ className, ...props }, ref) => ( 39 |
44 | )) 45 | AlertTitle.displayName = "AlertTitle" 46 | 47 | const AlertDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )) 57 | AlertDescription.displayName = "AlertDescription" 58 | 59 | export { Alert, AlertTitle, AlertDescription } 60 | -------------------------------------------------------------------------------- /src/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 2 | 3 | const AspectRatio = AspectRatioPrimitive.Root 4 | 5 | export { AspectRatio } 6 | -------------------------------------------------------------------------------- /src/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 3 | 4 | import { cn } from "src/lib/utils" 5 | 6 | const Avatar = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, ...props }, ref) => ( 10 | 18 | )) 19 | Avatar.displayName = AvatarPrimitive.Root.displayName 20 | 21 | const AvatarImage = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, ...props }, ref) => ( 25 | 30 | )) 31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 32 | 33 | const AvatarFallback = React.forwardRef< 34 | React.ElementRef, 35 | React.ComponentPropsWithoutRef 36 | >(({ className, ...props }, ref) => ( 37 | 45 | )) 46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 47 | 48 | export { Avatar, AvatarImage, AvatarFallback } 49 | -------------------------------------------------------------------------------- /src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "src/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /src/components/ui/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { ChevronRight, MoreHorizontal } from "lucide-react" 4 | 5 | import { cn } from "src/lib/utils" 6 | 7 | const Breadcrumb = React.forwardRef< 8 | HTMLElement, 9 | React.ComponentPropsWithoutRef<"nav"> & { 10 | separator?: React.ReactNode 11 | } 12 | >(({ ...props }, ref) =>