xertica-ui 2.0.0 → 2.0.2

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.
package/dist/cli.js CHANGED
@@ -528,7 +528,7 @@ var generateTokensCss = (theme) => {
528
528
  var __filename = fileURLToPath(import.meta.url);
529
529
  var __dirname = path.dirname(__filename);
530
530
  var program = new Command();
531
- program.name("xertica-ui").description("CLI to initialize Xertica UI projects").version("1.7.0");
531
+ program.name("xertica-ui").description("CLI to initialize Xertica UI projects").version("2.0.2");
532
532
  program.command("init").description("Initialize a new Xertica UI project").argument("[directory]", "Directory to initialize in", ".").action(async (directory) => {
533
533
  const targetDir = path.resolve(process.cwd(), directory);
534
534
  const templatesDir = path.resolve(__dirname, "../templates");
@@ -583,9 +583,8 @@ program.command("init").description("Initialize a new Xertica UI project").argum
583
583
  ];
584
584
  for (const file of rootFilesToCopy) {
585
585
  const srcPath = path.join(templatesDir, file);
586
- const destPath = path.join(targetDir, file);
587
586
  if (await fs.pathExists(srcPath)) {
588
- await fs.copy(srcPath, destPath);
587
+ await fs.copy(srcPath, path.join(targetDir, file));
589
588
  }
590
589
  }
591
590
  const pkgTemplatePath = path.join(templatesDir, "package.json");
@@ -595,86 +594,136 @@ program.command("init").description("Initialize a new Xertica UI project").argum
595
594
  pkgContent.name = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
596
595
  await fs.writeJson(path.join(targetDir, "package.json"), pkgContent, { spaces: 2 });
597
596
  }
597
+ await fs.copy(
598
+ path.join(templatesDir, "src", "main.tsx"),
599
+ path.join(targetDir, "src", "main.tsx")
600
+ );
598
601
  await fs.ensureDir(path.join(targetDir, "src", "app"));
599
602
  await fs.copy(
600
603
  path.join(templatesDir, "src", "app", "App.tsx"),
601
604
  path.join(targetDir, "src", "app", "App.tsx")
602
605
  );
606
+ await fs.ensureDir(path.join(targetDir, "src", "app", "components"));
603
607
  await fs.copy(
604
- path.join(templatesDir, "src", "main.tsx"),
605
- path.join(targetDir, "src", "main.tsx")
608
+ path.join(templatesDir, "src", "app", "components", "AppLayout.tsx"),
609
+ path.join(targetDir, "src", "app", "components", "AppLayout.tsx")
606
610
  );
607
611
  await fs.copy(
608
- path.join(templatesDir, "src", "app", "routes.tsx"),
609
- path.join(targetDir, "src", "app", "routes.tsx")
612
+ path.join(templatesDir, "src", "shared"),
613
+ path.join(targetDir, "src", "shared")
610
614
  );
611
- await fs.ensureDir(path.join(targetDir, "src", "app", "pages"));
612
- const optionalPages = {
613
- login: { file: "LoginPage.tsx", dir: "Login", shouldCopy: hasLogin },
614
- forgot: { file: "ForgotPasswordPage.tsx", dir: "ForgotPassword", shouldCopy: hasLogin },
615
- verify: { file: "VerifyEmailPage.tsx", dir: "VerifyEmail", shouldCopy: hasLogin },
616
- reset: { file: "ResetPasswordPage.tsx", dir: "ResetPassword", shouldCopy: hasLogin },
617
- home: { file: "HomePage.tsx", dir: "Home", shouldCopy: hasHome },
618
- template: { file: "TemplatePage.tsx", dir: "Template", shouldCopy: hasTemplate }
619
- };
620
- const srcPagesDir = path.join(templatesDir, "src", "app", "pages");
621
- for (const key in optionalPages) {
622
- const { file, dir, shouldCopy } = optionalPages[key];
623
- if (shouldCopy) {
624
- const srcFilePath = path.join(srcPagesDir, file);
625
- if (await fs.pathExists(srcFilePath)) {
626
- await fs.copy(srcFilePath, path.join(targetDir, "src", "app", "pages", file));
627
- }
628
- if (dir) {
629
- const srcDirPath = path.join(srcPagesDir, dir);
630
- if (await fs.pathExists(srcDirPath)) {
631
- await fs.copy(srcDirPath, path.join(targetDir, "src", "app", "pages", dir));
632
- }
633
- }
634
- }
615
+ if (hasLogin) {
616
+ await fs.copy(
617
+ path.join(templatesDir, "src", "features", "auth"),
618
+ path.join(targetDir, "src", "features", "auth")
619
+ );
635
620
  }
636
- if (!hasLogin || !hasHome || !hasTemplate) {
637
- const imports = [];
638
- const routes = [];
639
- if (hasLogin) {
640
- imports.push(`import { LoginPage } from './pages/LoginPage';`);
641
- imports.push(`import { ForgotPasswordPage } from './pages/ForgotPasswordPage';`);
642
- imports.push(`import { VerifyEmailPage } from './pages/VerifyEmailPage';`);
643
- imports.push(`import { ResetPasswordPage } from './pages/ResetPasswordPage';`);
644
- routes.push(` <Route path="/login" element={<LoginPage onLogin={handleLogin} />} />`);
645
- routes.push(` <Route path="/forgot-password" element={<ForgotPasswordPage />} />`);
646
- routes.push(` <Route path="/verify-email" element={<VerifyEmailPage />} />`);
647
- routes.push(` <Route path="/reset-password" element={<ResetPasswordPage />} />`);
648
- }
649
- if (hasHome) {
650
- imports.push(`import { HomePage } from './pages/HomePage';`);
651
- routes.push(` <Route path="/home" element={<HomePage user={user} onLogout={handleLogout} />} />`);
652
- }
653
- if (hasTemplate) {
654
- imports.push(`import { TemplatePage } from './pages/TemplatePage';`);
655
- routes.push(` <Route path="/template" element={<TemplatePage user={user} onLogout={handleLogout} />} />`);
621
+ if (hasHome) {
622
+ await fs.copy(
623
+ path.join(templatesDir, "src", "features", "home"),
624
+ path.join(targetDir, "src", "features", "home")
625
+ );
626
+ }
627
+ if (hasTemplate) {
628
+ await fs.copy(
629
+ path.join(templatesDir, "src", "features", "template"),
630
+ path.join(targetDir, "src", "features", "template")
631
+ );
632
+ }
633
+ await fs.ensureDir(path.join(targetDir, "src", "pages"));
634
+ const pagesToCopy = [];
635
+ if (hasLogin) pagesToCopy.push("LoginPage.tsx", "ForgotPasswordPage.tsx", "VerifyEmailPage.tsx", "ResetPasswordPage.tsx");
636
+ if (hasHome) pagesToCopy.push("HomePage.tsx");
637
+ if (hasTemplate) pagesToCopy.push("TemplatePage.tsx");
638
+ for (const pageFile of pagesToCopy) {
639
+ const src = path.join(templatesDir, "src", "pages", pageFile);
640
+ if (await fs.pathExists(src)) {
641
+ await fs.copy(src, path.join(targetDir, "src", "pages", pageFile));
656
642
  }
657
- const minimalApp = `import React, { useState, useEffect, useLayoutEffect } from 'react';
658
- import { BrowserRouter as Router, Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom';
659
- import { XerticaProvider } from 'xertica-ui';
660
- ${imports.join("\n")}
643
+ }
644
+ const firstProtectedPath = hasHome ? "/home" : hasTemplate ? "/template" : "/login";
645
+ const authGuardImports = [
646
+ `import React, { useState, useEffect } from 'react';`,
647
+ `import { Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom';`
648
+ ];
649
+ if (hasLogin) {
650
+ authGuardImports.push(`import { getStoredUser, storeUser, clearStoredUser } from '../../shared/lib/auth';`);
651
+ authGuardImports.push(`import type { User } from '../../shared/types/auth';`);
652
+ authGuardImports.push(`import { LoginPage } from '../../pages/LoginPage';`);
653
+ authGuardImports.push(`import { ForgotPasswordPage } from '../../pages/ForgotPasswordPage';`);
654
+ authGuardImports.push(`import { VerifyEmailPage } from '../../pages/VerifyEmailPage';`);
655
+ authGuardImports.push(`import { ResetPasswordPage } from '../../pages/ResetPasswordPage';`);
656
+ } else {
657
+ authGuardImports.push(`import { getStoredUser, storeUser, clearStoredUser } from '../../shared/lib/auth';`);
658
+ authGuardImports.push(`import type { User } from '../../shared/types/auth';`);
659
+ }
660
+ if (hasHome) authGuardImports.push(`import { HomePage } from '../../pages/HomePage';`);
661
+ if (hasTemplate) authGuardImports.push(`import { TemplatePage } from '../../pages/TemplatePage';`);
662
+ const protectedRoutes = [];
663
+ if (hasHome) protectedRoutes.push(` <Route path="/home" element={<ProtectedRoute user={user}><HomePage user={user} onLogout={handleLogout} /></ProtectedRoute>} />`);
664
+ if (hasTemplate) protectedRoutes.push(` <Route path="/template" element={<ProtectedRoute user={user}><TemplatePage user={user} onLogout={handleLogout} /></ProtectedRoute>} />`);
665
+ const authRoutes = [];
666
+ if (hasLogin) {
667
+ authRoutes.push(` <Route path="/login" element={user ? <Navigate to="${firstProtectedPath}" replace /> : <LoginPage onLogin={handleLogin} />} />`);
668
+ authRoutes.push(` <Route path="/forgot-password" element={user ? <Navigate to="${firstProtectedPath}" replace /> : <ForgotPasswordPage />} />`);
669
+ authRoutes.push(` <Route path="/verify-email" element={user ? <Navigate to="${firstProtectedPath}" replace /> : <VerifyEmailPage />} />`);
670
+ authRoutes.push(` <Route path="/reset-password" element={user ? <Navigate to="${firstProtectedPath}" replace /> : <ResetPasswordPage />} />`);
671
+ }
672
+ const handleLoginFn = hasLogin ? `
673
+ const handleLogin = (email: string, password: string): boolean => {
674
+ if (!email.trim() || !password.trim()) return false;
675
+ const userData: User = { email };
676
+ storeUser(userData);
677
+ setUser(userData);
678
+ navigate('${firstProtectedPath}');
679
+ return true;
680
+ };` : "";
681
+ const authPathsRedirect = hasLogin ? `
682
+ useEffect(() => {
683
+ const authPaths = ['/login', '/forgot-password', '/verify-email', '/reset-password'];
684
+ if (user && authPaths.includes(location.pathname)) {
685
+ navigate('${firstProtectedPath}', { replace: true });
686
+ }
687
+ }, [user, location.pathname, navigate]);` : "";
688
+ const authGuardContent = `${authGuardImports.join("\n")}
689
+
690
+ function ProtectedRoute({ children, user }: { children: React.ReactNode; user: User | null }) {
691
+ if (!user) return <Navigate to="${hasLogin ? "/login" : firstProtectedPath}" replace />;
692
+ return <>{children}</>;
693
+ }
694
+
695
+ export function AuthGuard() {
696
+ const [user, setUser] = useState<User | null>(null);
697
+ const navigate = useNavigate();${hasLogin ? `
698
+ const location = useLocation();` : ""}
699
+
700
+ useEffect(() => {
701
+ setUser(getStoredUser());
702
+ }, []);
703
+ ${authPathsRedirect}${handleLoginFn}
704
+
705
+ const handleLogout = () => {
706
+ clearStoredUser();
707
+ setUser(null);
708
+ navigate('${hasLogin ? "/login" : firstProtectedPath}');
709
+ };
661
710
 
662
- export default function App() {
663
711
  return (
664
- <XerticaProvider>
665
- <Router>
666
- <Routes>
667
- ${routes.join("\n")}
668
- <Route path="/" element={<Navigate to="${hasLogin ? "/login" : hasHome ? "/home" : "/template"}" replace />} />
669
- <Route path="*" element={<Navigate to="${hasLogin ? "/login" : hasHome ? "/home" : "/template"}" replace />} />
670
- </Routes>
671
- </Router>
672
- </XerticaProvider>
712
+ <div className="min-h-screen bg-muted overflow-x-hidden max-w-full">
713
+ <Routes>
714
+ ${authRoutes.join("\n")}
715
+ ${protectedRoutes.join("\n")}
716
+ <Route path="/" element={<Navigate to="${hasLogin ? firstProtectedPath === "/login" ? "/login" : firstProtectedPath : firstProtectedPath}" replace />} />
717
+ <Route path="*" element={<Navigate to="${hasLogin ? "/login" : firstProtectedPath}" replace />} />
718
+ </Routes>
719
+ </div>
673
720
  );
674
721
  }
675
722
  `;
676
- await fs.writeFile(path.join(targetDir, "src", "app", "App.tsx"), minimalApp);
677
- }
723
+ await fs.writeFile(
724
+ path.join(targetDir, "src", "app", "components", "AuthGuard.tsx"),
725
+ authGuardContent
726
+ );
678
727
  const selectedTheme = colorThemes.find((t) => t.id === response.theme) || colorThemes[0];
679
728
  const tokensDir = path.join(targetDir, "src", "styles", "xertica");
680
729
  await fs.ensureDir(tokensDir);
@@ -682,8 +731,7 @@ ${routes.join("\n")}
682
731
  path.join(templatesDir, "src", "styles", "index.css"),
683
732
  path.join(targetDir, "src", "styles", "index.css")
684
733
  );
685
- const newTokensCss = generateTokensCss(selectedTheme);
686
- await fs.writeFile(path.join(tokensDir, "tokens.css"), newTokensCss);
734
+ await fs.writeFile(path.join(tokensDir, "tokens.css"), generateTokensCss(selectedTheme));
687
735
  spinner.succeed("Project initialized successfully!");
688
736
  if (response.install) {
689
737
  const installSpinner = ora("Installing dependencies...").start();
@@ -705,34 +753,151 @@ ${routes.join("\n")}
705
753
  console.error(error);
706
754
  }
707
755
  });
708
- program.command("update").alias("update-theme").description("Update theme tokens in your project").action(async () => {
756
+ program.command("update").alias("update-theme").description("Update theme or project files to the latest version").action(async () => {
709
757
  const targetDir = process.cwd();
710
- const themeResponse = await prompts({
758
+ const { updateType } = await prompts({
759
+ type: "select",
760
+ name: "updateType",
761
+ message: "What do you want to update?",
762
+ choices: [
763
+ { title: "Theme only", description: "Change the color tokens (tokens.css)", value: "theme" },
764
+ { title: "Project files", description: "Update app shell, shared, features and pages to a specific version", value: "project" }
765
+ ]
766
+ });
767
+ if (!updateType) return;
768
+ if (updateType === "theme") {
769
+ const { theme } = await prompts({
770
+ type: "select",
771
+ name: "theme",
772
+ message: "Select the new color theme:",
773
+ choices: colorThemes.map((t) => ({
774
+ title: t.name,
775
+ description: t.description,
776
+ value: t.id
777
+ })),
778
+ initial: 0
779
+ });
780
+ if (!theme) return;
781
+ const spinner2 = ora("Updating theme...").start();
782
+ try {
783
+ const tokensPath = path.join(targetDir, "src", "styles", "xertica", "tokens.css");
784
+ const selectedTheme = colorThemes.find((t) => t.id === theme);
785
+ if (selectedTheme) {
786
+ await fs.ensureDir(path.dirname(tokensPath));
787
+ await fs.writeFile(tokensPath, generateTokensCss(selectedTheme));
788
+ spinner2.succeed(`Theme updated to "${selectedTheme.name}" successfully!`);
789
+ } else {
790
+ spinner2.fail("Theme not found.");
791
+ }
792
+ } catch (error) {
793
+ spinner2.fail("Failed to update theme");
794
+ console.error(error);
795
+ }
796
+ return;
797
+ }
798
+ const { versionType } = await prompts({
711
799
  type: "select",
712
- name: "theme",
713
- message: "Select the new color theme:",
714
- choices: colorThemes.map((t) => ({
715
- title: t.name,
716
- description: t.description,
717
- value: t.id
718
- })),
719
- initial: 0
800
+ name: "versionType",
801
+ message: "Which version do you want to update to?",
802
+ choices: [
803
+ { title: "Latest", description: "Install the latest published version", value: "latest" },
804
+ { title: "Specific version", description: "Enter a version number (e.g. 2.0.2)", value: "specific" }
805
+ ]
806
+ });
807
+ if (!versionType) return;
808
+ let targetVersion = "latest";
809
+ if (versionType === "specific") {
810
+ const { version } = await prompts({
811
+ type: "text",
812
+ name: "version",
813
+ message: "Enter the version (e.g. 2.0.2):",
814
+ validate: (v) => /^\d+\.\d+\.\d+/.test(v.trim()) ? true : "Enter a valid semver (e.g. 2.0.2)"
815
+ });
816
+ if (!version) return;
817
+ targetVersion = version.trim();
818
+ }
819
+ const { filesToUpdate } = await prompts({
820
+ type: "multiselect",
821
+ name: "filesToUpdate",
822
+ message: "Select which parts of the project to update:",
823
+ choices: [
824
+ { title: "App shell (src/app/)", description: "App.tsx, AppLayout.tsx", value: "app", selected: true },
825
+ { title: "Shared utilities (src/shared/)", description: "auth.ts, navigation.ts, types", value: "shared", selected: true },
826
+ { title: "Features (src/features/)", description: "auth, home, template UI components", value: "features", selected: true },
827
+ { title: "Pages (src/pages/)", description: "Thin page wrapper components", value: "pages", selected: true },
828
+ { title: "Root config files", description: "vite.config.ts, tsconfig.json, etc.", value: "config", selected: false }
829
+ ]
720
830
  });
721
- if (!themeResponse.theme) return;
722
- const spinner = ora("Updating theme...").start();
831
+ if (!filesToUpdate || filesToUpdate.length === 0) return;
832
+ const { confirmed } = await prompts({
833
+ type: "confirm",
834
+ name: "confirmed",
835
+ message: chalk.yellow(`\u26A0\uFE0F This will overwrite the selected files. Local changes will be lost. Continue?`),
836
+ initial: false
837
+ });
838
+ if (!confirmed) {
839
+ console.log(chalk.gray("Update cancelled."));
840
+ return;
841
+ }
842
+ const spinner = ora(`Installing xertica-ui@${targetVersion}...`).start();
723
843
  try {
724
- const tokensPath = path.join(targetDir, "src", "styles", "xertica", "tokens.css");
725
- const selectedTheme = colorThemes.find((t) => t.id === themeResponse.theme);
726
- if (selectedTheme) {
727
- await fs.ensureDir(path.dirname(tokensPath));
728
- const newTokensCss = generateTokensCss(selectedTheme);
729
- await fs.writeFile(tokensPath, newTokensCss);
730
- spinner.succeed(`Theme updated to "${selectedTheme.name}" successfully!`);
731
- } else {
732
- spinner.fail("Theme not found.");
844
+ await execa("npm", ["install", `xertica-ui@${targetVersion}`], { cwd: targetDir });
845
+ spinner.text = "Copying updated files...";
846
+ const updatedTemplatesDir = path.join(targetDir, "node_modules", "xertica-ui", "templates");
847
+ if (filesToUpdate.includes("app")) {
848
+ await fs.copy(
849
+ path.join(updatedTemplatesDir, "src", "app", "App.tsx"),
850
+ path.join(targetDir, "src", "app", "App.tsx"),
851
+ { overwrite: true }
852
+ );
853
+ await fs.copy(
854
+ path.join(updatedTemplatesDir, "src", "app", "components", "AppLayout.tsx"),
855
+ path.join(targetDir, "src", "app", "components", "AppLayout.tsx"),
856
+ { overwrite: true }
857
+ );
858
+ }
859
+ if (filesToUpdate.includes("shared")) {
860
+ await fs.copy(
861
+ path.join(updatedTemplatesDir, "src", "shared"),
862
+ path.join(targetDir, "src", "shared"),
863
+ { overwrite: true }
864
+ );
865
+ }
866
+ if (filesToUpdate.includes("features")) {
867
+ for (const feature of ["auth", "home", "template"]) {
868
+ const destFeature = path.join(targetDir, "src", "features", feature);
869
+ const srcFeature = path.join(updatedTemplatesDir, "src", "features", feature);
870
+ if (await fs.pathExists(destFeature) && await fs.pathExists(srcFeature)) {
871
+ await fs.copy(srcFeature, destFeature, { overwrite: true });
872
+ }
873
+ }
874
+ }
875
+ if (filesToUpdate.includes("pages")) {
876
+ const pagesDir = path.join(targetDir, "src", "pages");
877
+ const srcPagesDir = path.join(updatedTemplatesDir, "src", "pages");
878
+ if (await fs.pathExists(pagesDir) && await fs.pathExists(srcPagesDir)) {
879
+ const existingPages = await fs.readdir(pagesDir);
880
+ for (const pageFile of existingPages) {
881
+ const src = path.join(srcPagesDir, pageFile);
882
+ if (await fs.pathExists(src)) {
883
+ await fs.copy(src, path.join(pagesDir, pageFile), { overwrite: true });
884
+ }
885
+ }
886
+ }
887
+ }
888
+ if (filesToUpdate.includes("config")) {
889
+ const configFiles = ["vite.config.ts", "tsconfig.json", "tsconfig.node.json", "postcss.config.js"];
890
+ for (const file of configFiles) {
891
+ const src = path.join(updatedTemplatesDir, "..", file);
892
+ if (await fs.pathExists(src)) {
893
+ await fs.copy(src, path.join(targetDir, file), { overwrite: true });
894
+ }
895
+ }
733
896
  }
897
+ spinner.succeed(`Project updated to xertica-ui@${targetVersion} successfully!`);
898
+ console.log(chalk.gray("\n Run npm run dev to start the development server."));
734
899
  } catch (error) {
735
- spinner.fail("Failed to update theme");
900
+ spinner.fail("Failed to update project");
736
901
  console.error(error);
737
902
  }
738
903
  });
@@ -3,3 +3,4 @@ export { CodeBlock } from './code-block';
3
3
  export { MarkdownMessage } from './markdown-message';
4
4
  export { ModernChatInput } from './modern-chat-input';
5
5
  export { FormattedDocument } from './formatted-document';
6
+ export { generateDemoResponse } from '../../utils/demo-responses';
@@ -0,0 +1,3 @@
1
+ import { Message } from '../components/assistant/xertica-assistant';
2
+ export declare const generateDemoResponse: (mensagemUsuario: string) => string | Partial<Message>;
3
+ export declare const getDemoResponse: (mensagemUsuario: string) => string | Partial<Message>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xertica-ui",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Xertica UI — Enterprise-grade React design system with Tailwind CSS v4, Radix UI, and AI-first documentation.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-xertica-app",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -17,7 +17,7 @@
17
17
  "react-dom": "^18.3.1",
18
18
  "react-router-dom": "^7.1.3",
19
19
  "sonner": "^1.7.3",
20
- "xertica-ui": "^2.0.0"
20
+ "xertica-ui": "^2.0.1"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@eslint/js": "^9.18.0",