create-tririga-react-ts-vite-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +28 -0
  2. package/index.js +138 -0
  3. package/package.json +22 -0
  4. package/template/.env.example +14 -0
  5. package/template/README.md +73 -0
  6. package/template/_gitignore +26 -0
  7. package/template/components.json +23 -0
  8. package/template/index.html +13 -0
  9. package/template/package.json +45 -0
  10. package/template/public/tri-app-config.json +9 -0
  11. package/template/public/vite.svg +1 -0
  12. package/template/src/App.tsx +15 -0
  13. package/template/src/components/MainLayout.tsx +64 -0
  14. package/template/src/components/index.ts +1 -0
  15. package/template/src/components/ui/accordion.tsx +64 -0
  16. package/template/src/components/ui/avatar.tsx +109 -0
  17. package/template/src/components/ui/badge.tsx +48 -0
  18. package/template/src/components/ui/button.tsx +64 -0
  19. package/template/src/components/ui/calendar.tsx +222 -0
  20. package/template/src/components/ui/card.tsx +92 -0
  21. package/template/src/components/ui/dialog.tsx +158 -0
  22. package/template/src/components/ui/input.tsx +21 -0
  23. package/template/src/components/ui/label.tsx +22 -0
  24. package/template/src/components/ui/popover.tsx +89 -0
  25. package/template/src/components/ui/select.tsx +188 -0
  26. package/template/src/components/ui/separator.tsx +28 -0
  27. package/template/src/components/ui/sonner.tsx +38 -0
  28. package/template/src/components/ui/switch.tsx +35 -0
  29. package/template/src/components/ui/tabs.tsx +89 -0
  30. package/template/src/components/ui/textarea.tsx +18 -0
  31. package/template/src/components/ui/tooltip.tsx +55 -0
  32. package/template/src/index.css +38 -0
  33. package/template/src/lib/utils.ts +6 -0
  34. package/template/src/main.tsx +68 -0
  35. package/template/src/model/AppModel.ts +14 -0
  36. package/template/src/pages/HomePage.tsx +15 -0
  37. package/template/src/pages/index.ts +1 -0
  38. package/template/src/services/AuthService.ts +58 -0
  39. package/template/src/services/tririgaService.ts +42 -0
  40. package/template/src/types/tririga-react-components.d.ts +107 -0
  41. package/template/tsconfig.json +25 -0
  42. package/template/vite.config.ts +16 -0
package/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # create-pb-tririga-react-app
2
+
3
+ A CLI tool to scaffold modern TRIRIGA UX applications using React, TypeScript, and Vite.
4
+
5
+ ## Usage
6
+
7
+ You don't need to install this package locally. You can run it directly using `npx` and replace `my-tririga-app` with your desired app name:
8
+
9
+ ```bash
10
+ npx create-pb-tririga-react-app my-tririga-app
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - **Framework**: React 18 + TypeScript
16
+ - **Build Tool**: Vite (Fast HMR & Bundling)
17
+ - **UI Library**: ShadCN
18
+ - **Routing**: HashRouter (Compatible with TRIRIGA context paths)
19
+ - **Deployment**: Ready-to-use scripts for `tri-deploy`
20
+
21
+ ## Getting Started with your new app
22
+
23
+ Once created:
24
+
25
+ 1. `cd my-tririga-app`
26
+ 2. Install pnpm `npm install -g pnpm`
27
+ 3. Install dependencies `pnpm install`
28
+ 4. Make your first deployment with `pnpm run build:deploy`
package/index.js ADDED
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const readline = require('readline');
6
+
7
+ // 1. Get project name from command line
8
+ const projectName = process.argv[2] || 'my-tririga-app';
9
+ const currentDir = process.cwd();
10
+ const projectDir = path.join(currentDir, projectName);
11
+
12
+ // 2. Create Directory
13
+ if (fs.existsSync(projectDir)) {
14
+ console.error(`Directory ${projectName} already exists.`);
15
+ process.exit(1);
16
+ }
17
+ fs.mkdirSync(projectDir);
18
+
19
+ // 3. Copy Template Files
20
+ const templateDir = path.join(__dirname, 'template');
21
+
22
+ function copyRecursiveSync(src, dest) {
23
+ const exists = fs.existsSync(src);
24
+ const stats = exists && fs.statSync(src);
25
+ const isDirectory = exists && stats.isDirectory();
26
+
27
+ if (isDirectory) {
28
+ fs.mkdirSync(dest, { recursive: true });
29
+ fs.readdirSync(src).forEach(childItemName => {
30
+ copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));
31
+ });
32
+ } else {
33
+ fs.copyFileSync(src, dest);
34
+ }
35
+ }
36
+
37
+ copyRecursiveSync(templateDir, projectDir);
38
+
39
+ // Rename _gitignore to .gitignore
40
+ const gitignorePath = path.join(projectDir, '_gitignore');
41
+ if (fs.existsSync(gitignorePath)) {
42
+ fs.renameSync(gitignorePath, path.join(projectDir, '.gitignore'));
43
+ }
44
+
45
+ // Helper function for user prompts
46
+ function askQuestion(query, hidden = false) {
47
+ const rl = readline.createInterface({
48
+ input: process.stdin,
49
+ output: process.stdout,
50
+ });
51
+
52
+ if (hidden) {
53
+ let stdinListener = (char) => {
54
+ char = char + '';
55
+ switch (char) {
56
+ case '\n':
57
+ case '\r':
58
+ case '\u0004':
59
+ process.stdin.removeListener('data', stdinListener);
60
+ break;
61
+ default:
62
+ process.stdout.write('\x1B[2K\x1B[200D' + query + Array(rl.line.length + 1).join('*'));
63
+ break;
64
+ }
65
+ };
66
+ process.stdin.on('data', stdinListener);
67
+ }
68
+
69
+ return new Promise((resolve) => rl.question(query, (ans) => {
70
+ rl.close();
71
+ if (hidden) console.log(); // Add newline after hidden input
72
+ resolve(ans);
73
+ }));
74
+ }
75
+
76
+ async function configureApp() {
77
+ console.log('\n--- Configuration Setup ---\n');
78
+
79
+ const triUrl = await askQuestion('TRIRIGA Environment URL (e.g., https://your-tririga-server.com): ');
80
+ const triUser = await askQuestion('TRIRIGA Username: ');
81
+ const triPassword = await askQuestion('TRIRIGA Password: ', true);
82
+ const appTitle = await askQuestion('App Title (e.g., TRIRIGA Service Request): ');
83
+ const appExposedName = await askQuestion('Application Exposed Name (e.g., myTririgaApp): ');
84
+ const modelAndView = await askQuestion('Model and View Name (e.g., myTririgaApp): ');
85
+ const webViewMetadataName = await askQuestion(`Web View Metadata Exposed Name (default: ${projectName}): `);
86
+
87
+ const finalWebViewName = webViewMetadataName || projectName;
88
+
89
+ // 4. Update package.json name with Web View Metadata Exposed Name
90
+ const pkgJsonPath = path.join(projectDir, 'package.json');
91
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
92
+ pkgJson.name = finalWebViewName;
93
+ fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
94
+
95
+ // 5. Update .env file
96
+ const envExamplePath = path.join(projectDir, '.env.example');
97
+ const envPath = path.join(projectDir, '.env');
98
+ if (fs.existsSync(envExamplePath)) {
99
+ let envContent = fs.readFileSync(envExamplePath, 'utf8');
100
+ envContent = envContent.replace(/TRI_URL=.*/g, `TRI_URL=${triUrl}`);
101
+ envContent = envContent.replace(/TRI_USER=.*/g, `TRI_USER=${triUser}`);
102
+ envContent = envContent.replace(/TRI_PASSWORD=.*/g, `TRI_PASSWORD=${triPassword}`);
103
+ fs.writeFileSync(envPath, envContent);
104
+ // Delete .env.example
105
+ fs.unlinkSync(envExamplePath);
106
+ }
107
+
108
+ // 6. Update tririgaService.ts
109
+ const tririgaServicePath = path.join(projectDir, 'src', 'services', 'tririgaService.ts');
110
+ if (fs.existsSync(tririgaServicePath)) {
111
+ let tsContent = fs.readFileSync(tririgaServicePath, 'utf8');
112
+ tsContent = tsContent.replace(/modelAndView:\s*"[^"]*"/, `modelAndView: "${modelAndView}"`);
113
+ tsContent = tsContent.replace(/appExposedName:\s*"[^"]*"/, `appExposedName: "${appExposedName}"`);
114
+ fs.writeFileSync(tririgaServicePath, tsContent);
115
+ }
116
+
117
+ // 7. Update vite.config.ts base path
118
+ const viteConfigPath = path.join(projectDir, 'vite.config.ts');
119
+ if (fs.existsSync(viteConfigPath)) {
120
+ let viteContent = fs.readFileSync(viteConfigPath, 'utf8');
121
+ // Regex matches the whole base line and replaces it, handling single or double quotes
122
+ viteContent = viteContent.replace(/base:\s*['"]\/app\/[^/]+\/['"],/, `base: '/app/${appExposedName}/',`);
123
+ fs.writeFileSync(viteConfigPath, viteContent);
124
+ }
125
+
126
+ // 8. Update index.html Title
127
+ const indexHtmlPath = path.join(projectDir, 'index.html');
128
+ if (fs.existsSync(indexHtmlPath) && appTitle) {
129
+ let htmlContent = fs.readFileSync(indexHtmlPath, 'utf8');
130
+ htmlContent = htmlContent.replace(/<title>.*<\/title>/, `<title>${appTitle}</title>`);
131
+ fs.writeFileSync(indexHtmlPath, htmlContent);
132
+ }
133
+
134
+ console.log(`\nSuccess! Created and configured ${projectName}.`);
135
+ console.log(`\nTo get started:\n cd ${projectName}\n npm install -g pnpm\n pnpm install\n pnpm run build:deploy\n`);
136
+ }
137
+
138
+ configureApp();
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "create-tririga-react-ts-vite-app",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold a TRIRIGA React app with Vite and TypeScript",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "create-tririga-react-ts-vite-app": "./index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "template",
12
+ "README.md"
13
+ ],
14
+ "keywords": [
15
+ "tririga",
16
+ "react",
17
+ "vite",
18
+ "scaffold"
19
+ ],
20
+ "author": "Noah Richard",
21
+ "license": "ISC"
22
+ }
@@ -0,0 +1,14 @@
1
+ # Local Development Config
2
+ VITE_TRIRIGA_URL=http://localhost:8001
3
+ VITE_INSTANCE_ID=-1
4
+ VITE_CONTEXT_PATH=/app
5
+ VITE_MODEL_AND_VIEW=devView
6
+ VITE_BASE_PATH=/app/devView
7
+ VITE_EXPOSED_NAME=devView
8
+ VITE_SSO=false
9
+ VITE_AUTO_LOGIN=true
10
+
11
+ # Deployment Config (used by tri-deploy)
12
+ TRI_URL=https://your-tririga-server.com
13
+ TRI_USER=your-username
14
+ TRI_PASSWORD=your-password
@@ -0,0 +1,73 @@
1
+ # TRIRIGA React App Template
2
+
3
+ This is a template for building IBM TRIRIGA UX applications using React, TypeScript, and Vite.
4
+
5
+ ## Getting Started
6
+
7
+ 1. **Install pnpm** (If not already installed)
8
+ ```
9
+ npm install -g pnpm
10
+ ```
11
+
12
+ 2. **Install Dependencies**
13
+ ```
14
+ pnpm install
15
+ ```
16
+
17
+
18
+ ## Deployment
19
+
20
+ To deploy to a TRIRIGA environment:
21
+
22
+ 1. **Install Depoyment Tool** (If not already installed globally)
23
+ ```
24
+ npm install -g @tririga/tri-deploy
25
+ ```
26
+ *Note: If you prefer not to install globally, you can add `@tririga/tri-deploy` to your devDependencies.*
27
+
28
+ 2. **Configure Credentials**
29
+ Copy `.env.example` to `.env` and update the values for your deployment settings.
30
+ ```
31
+ cp .env.example .env
32
+ ```
33
+ Ensure your `.env` file has the correct `TRI_URL`, `TRI_USER`, and `TRI_PASSWORD`.
34
+
35
+ 3. **Configure tririgaService.js file**
36
+ Update the TriAppConfig values `modelAndView` and `appExposedName` to match your app configuration in TRIRIGA.
37
+ Example:
38
+ `modelAndView: "myTririgaApp",`
39
+ `appExposedName: "myTririgaApp",`
40
+
41
+ 4. **Configure vite.config.ts file**
42
+ Update the base value to match your application's exposed name in TRIRIGA.
43
+ Example:
44
+ base: '/app/myReactApp/',
45
+
46
+ 5. **Configure package.json**
47
+ "name": "my-tririga-app",
48
+
49
+ *Note: the name should match your Web View Metadata Exposed Name n TRIRIGA*
50
+
51
+ 6. **Run Deployment**
52
+ The view name will be taken from the `name` field in `package.json`.
53
+ ```
54
+ pnpm run build:deploy
55
+ ```
56
+
57
+ ## Browser Support & Polyfills
58
+
59
+ ## Project Structure
60
+
61
+ - `src/components`: Reusable UI components.
62
+ - `src/pages`: Top-level page definitions (routes).
63
+ - `src/model`: TRIRIGA AppModel (Datasources).
64
+ - `src/services`: API services (Auth, Config).
65
+ - `public/tri-app-config.json`: Local development configuration mocking the server response.
66
+
67
+ ## Tech Stack
68
+
69
+ - [React](https://react.dev/)
70
+ - [TypeScript](https://www.typescriptlang.org/)
71
+ - [Vite](https://vitejs.dev/)
72
+ - [Carbon Design System](https://carbondesignsystem.com/)
73
+ - [@tririga/tririga-react-components](https://www.npmjs.com/package/@tririga/tririga-react-components)
@@ -0,0 +1,26 @@
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
+ .env
15
+ ux-work-task-form
16
+
17
+ # Editor directories and files
18
+ .vscode/*
19
+ !.vscode/extensions.json
20
+ .idea
21
+ .DS_Store
22
+ *.suo
23
+ *.ntvs*
24
+ *.njsproj
25
+ *.sln
26
+ *.sw?
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/index.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "rtl": false,
15
+ "aliases": {
16
+ "components": "@/components",
17
+ "utils": "@/lib/utils",
18
+ "ui": "@/components/ui",
19
+ "lib": "@/lib",
20
+ "hooks": "@/hooks"
21
+ },
22
+ "registries": {}
23
+ }
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Tririga React App</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "my-tririga-app",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview",
11
+ "deploy": "dotenv -- cross-var tri-deploy -t %TRI_URL% -u %TRI_USER% -p %TRI_PASSWORD% -v \"$npm_package_name\" -d dist -w",
12
+ "build:deploy": "pnpm run build && pnpm run deploy"
13
+ },
14
+ "dependencies": {
15
+ "@tririga/tririga-react-components": "^2.0.6",
16
+ "class-variance-authority": "^0.7.1",
17
+ "clsx": "^2.1.1",
18
+ "lodash": "^4.17.21",
19
+ "lodash-es": "^4.17.21",
20
+ "lucide-react": "^0.575.0",
21
+ "react": "^18.3.1",
22
+ "react-dom": "^18.3.1",
23
+ "react-router-dom": "^6.28.0",
24
+ "tailwind-merge": "^3.5.0",
25
+ "tailwindcss": "^4.2.0"
26
+ },
27
+ "devDependencies": {
28
+ "@eslint/js": "^9.17.0",
29
+ "@types/react": "^18.3.18",
30
+ "@types/react-dom": "^18.3.5",
31
+ "@tailwindcss/vite": "^4.2.0",
32
+ "@types/node": "^22.0.0",
33
+ "@vitejs/plugin-react": "^4.3.4",
34
+ "cross-var": "^1.1.0",
35
+ "dotenv-cli": "^8.0.0",
36
+ "eslint": "^9.17.0",
37
+ "eslint-plugin-react-hooks": "^5.0.0",
38
+ "eslint-plugin-react-refresh": "^0.4.16",
39
+ "globals": "^15.14.0",
40
+ "shadcn": "^3.8.5",
41
+ "typescript": "~5.6.2",
42
+ "typescript-eslint": "^8.18.2",
43
+ "vite": "^6.0.5"
44
+ }
45
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "instanceId": "-1",
3
+ "tririgaUrl": "http://localhost:8001",
4
+ "contextPath": "/app",
5
+ "modelAndView": "devView",
6
+ "appPath": "/app/devView",
7
+ "appExposedName": "devView",
8
+ "sso": false
9
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
@@ -0,0 +1,15 @@
1
+ import { Routes, Route } from 'react-router-dom';
2
+ import { MainLayout } from './components';
3
+ import { HomePage } from './pages';
4
+
5
+ function App() {
6
+ return (
7
+ <Routes>
8
+ <Route path="/" element={<MainLayout />}>
9
+ <Route index element={<HomePage />} />
10
+ </Route>
11
+ </Routes>
12
+ );
13
+ }
14
+
15
+ export default App
@@ -0,0 +1,64 @@
1
+ import React from 'react';
2
+ import { Outlet, Link } from 'react-router-dom';
3
+ import { User, LogOut } from 'lucide-react';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Avatar, AvatarFallback } from '@/components/ui/avatar';
6
+ import {
7
+ Tooltip,
8
+ TooltipContent,
9
+ TooltipProvider,
10
+ TooltipTrigger,
11
+ } from '@/components/ui/tooltip';
12
+ import { AuthService } from '../services/AuthService';
13
+
14
+ const MainLayout: React.FC = () => {
15
+ const userName = 'User';
16
+
17
+ return (
18
+ <div className="min-h-screen flex flex-col">
19
+ <header className="sticky top-0 z-50 flex items-center justify-between px-6 h-12 bg-[#001529]">
20
+ <Link to="/" className="no-underline flex items-center gap-2">
21
+ <span className="font-semibold text-white/65 text-sm">TRIRIGA</span>
22
+ <span className="text-white text-sm">React App</span>
23
+ </Link>
24
+
25
+ <TooltipProvider>
26
+ <div className="flex items-center gap-1">
27
+ <Tooltip>
28
+ <TooltipTrigger asChild>
29
+ <Button variant="ghost" size="icon" className="text-white/85 hover:text-white hover:bg-white/10 h-8 w-8">
30
+ <Avatar className="h-6 w-6 bg-transparent">
31
+ <AvatarFallback className="bg-transparent text-white/85 text-xs">
32
+ <User className="h-4 w-4" />
33
+ </AvatarFallback>
34
+ </Avatar>
35
+ </Button>
36
+ </TooltipTrigger>
37
+ <TooltipContent>{userName}</TooltipContent>
38
+ </Tooltip>
39
+
40
+ <Tooltip>
41
+ <TooltipTrigger asChild>
42
+ <Button
43
+ variant="ghost"
44
+ size="icon"
45
+ className="text-white/85 hover:text-white hover:bg-white/10 h-8 w-8"
46
+ onClick={() => AuthService.logout()}
47
+ >
48
+ <LogOut className="h-4 w-4" />
49
+ </Button>
50
+ </TooltipTrigger>
51
+ <TooltipContent>Sign out</TooltipContent>
52
+ </Tooltip>
53
+ </div>
54
+ </TooltipProvider>
55
+ </header>
56
+
57
+ <main className="flex-1 p-6 bg-background min-h-[calc(100vh-48px)]">
58
+ <Outlet />
59
+ </main>
60
+ </div>
61
+ );
62
+ };
63
+
64
+ export default MainLayout;
@@ -0,0 +1 @@
1
+ export { default as MainLayout } from './MainLayout';
@@ -0,0 +1,64 @@
1
+ import * as React from "react"
2
+ import { ChevronDownIcon } from "lucide-react"
3
+ import { Accordion as AccordionPrimitive } from "radix-ui"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ function Accordion({
8
+ ...props
9
+ }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
10
+ return <AccordionPrimitive.Root data-slot="accordion" {...props} />
11
+ }
12
+
13
+ function AccordionItem({
14
+ className,
15
+ ...props
16
+ }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
17
+ return (
18
+ <AccordionPrimitive.Item
19
+ data-slot="accordion-item"
20
+ className={cn("border-b last:border-b-0", className)}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+
26
+ function AccordionTrigger({
27
+ className,
28
+ children,
29
+ ...props
30
+ }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
31
+ return (
32
+ <AccordionPrimitive.Header className="flex">
33
+ <AccordionPrimitive.Trigger
34
+ data-slot="accordion-trigger"
35
+ className={cn(
36
+ "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
37
+ className
38
+ )}
39
+ {...props}
40
+ >
41
+ {children}
42
+ <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
43
+ </AccordionPrimitive.Trigger>
44
+ </AccordionPrimitive.Header>
45
+ )
46
+ }
47
+
48
+ function AccordionContent({
49
+ className,
50
+ children,
51
+ ...props
52
+ }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
53
+ return (
54
+ <AccordionPrimitive.Content
55
+ data-slot="accordion-content"
56
+ className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
57
+ {...props}
58
+ >
59
+ <div className={cn("pt-0 pb-4", className)}>{children}</div>
60
+ </AccordionPrimitive.Content>
61
+ )
62
+ }
63
+
64
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
@@ -0,0 +1,109 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Avatar as AvatarPrimitive } from "radix-ui"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Avatar({
9
+ className,
10
+ size = "default",
11
+ ...props
12
+ }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
13
+ size?: "default" | "sm" | "lg"
14
+ }) {
15
+ return (
16
+ <AvatarPrimitive.Root
17
+ data-slot="avatar"
18
+ data-size={size}
19
+ className={cn(
20
+ "group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ function AvatarImage({
29
+ className,
30
+ ...props
31
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
32
+ return (
33
+ <AvatarPrimitive.Image
34
+ data-slot="avatar-image"
35
+ className={cn("aspect-square size-full", className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function AvatarFallback({
42
+ className,
43
+ ...props
44
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
45
+ return (
46
+ <AvatarPrimitive.Fallback
47
+ data-slot="avatar-fallback"
48
+ className={cn(
49
+ "bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs",
50
+ className
51
+ )}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+
57
+ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
58
+ return (
59
+ <span
60
+ data-slot="avatar-badge"
61
+ className={cn(
62
+ "bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full ring-2 select-none",
63
+ "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
64
+ "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
65
+ "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
66
+ className
67
+ )}
68
+ {...props}
69
+ />
70
+ )
71
+ }
72
+
73
+ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
74
+ return (
75
+ <div
76
+ data-slot="avatar-group"
77
+ className={cn(
78
+ "*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
79
+ className
80
+ )}
81
+ {...props}
82
+ />
83
+ )
84
+ }
85
+
86
+ function AvatarGroupCount({
87
+ className,
88
+ ...props
89
+ }: React.ComponentProps<"div">) {
90
+ return (
91
+ <div
92
+ data-slot="avatar-group-count"
93
+ className={cn(
94
+ "bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
95
+ className
96
+ )}
97
+ {...props}
98
+ />
99
+ )
100
+ }
101
+
102
+ export {
103
+ Avatar,
104
+ AvatarImage,
105
+ AvatarFallback,
106
+ AvatarBadge,
107
+ AvatarGroup,
108
+ AvatarGroupCount,
109
+ }