create-bdpa-react-scaffold 1.8.8 → 1.9.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.
- package/BDPA_edited.png +0 -0
- package/create-ui-lib.js +219 -437
- package/package.json +2 -2
- package/BDPA_edited.avif +0 -0
package/BDPA_edited.png
ADDED
|
Binary file
|
package/create-ui-lib.js
CHANGED
|
@@ -354,9 +354,15 @@ module.exports = {
|
|
|
354
354
|
write("vite.config.mts", `
|
|
355
355
|
import { defineConfig } from "vite";
|
|
356
356
|
import react from "@vitejs/plugin-react-swc";
|
|
357
|
+
import path from "path";
|
|
357
358
|
|
|
358
359
|
export default defineConfig({
|
|
359
360
|
plugins: [react()],
|
|
361
|
+
resolve: {
|
|
362
|
+
alias: {
|
|
363
|
+
"@": path.resolve(__dirname, "./src"),
|
|
364
|
+
},
|
|
365
|
+
},
|
|
360
366
|
server: {
|
|
361
367
|
host: true,
|
|
362
368
|
port: 3000
|
|
@@ -369,6 +375,7 @@ write("index.html", `
|
|
|
369
375
|
<html lang="en">
|
|
370
376
|
<head>
|
|
371
377
|
<meta charset="UTF-8" />
|
|
378
|
+
<link rel="icon" type="image/png" href="/BDPA_edited.png" />
|
|
372
379
|
<title>BDPA React Scaffold and Demo</title>
|
|
373
380
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
374
381
|
</head>
|
|
@@ -390,7 +397,7 @@ write("HOWTO.md", `
|
|
|
390
397
|
// src/pages/Dashboard.jsx
|
|
391
398
|
import React from "react";
|
|
392
399
|
import Container from "../components/layout/Container";
|
|
393
|
-
import Card from "
|
|
400
|
+
import { Card, CardContent } from "@/components/ui/card";
|
|
394
401
|
|
|
395
402
|
export default function Dashboard() {
|
|
396
403
|
return (
|
|
@@ -622,14 +629,13 @@ import ReactDOM from "react-dom/client";
|
|
|
622
629
|
import { BrowserRouter } from "react-router-dom";
|
|
623
630
|
import App from "./App.jsx";
|
|
624
631
|
import "./index.css";
|
|
625
|
-
import {
|
|
632
|
+
import { Toaster } from "@/components/ui/sonner";
|
|
626
633
|
|
|
627
634
|
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
628
635
|
<React.StrictMode>
|
|
629
636
|
<BrowserRouter>
|
|
630
|
-
<
|
|
631
|
-
|
|
632
|
-
</ToastProvider>
|
|
637
|
+
<App />
|
|
638
|
+
<Toaster />
|
|
633
639
|
</BrowserRouter>
|
|
634
640
|
</React.StrictMode>
|
|
635
641
|
);
|
|
@@ -651,15 +657,6 @@ export function cn(...inputs) {
|
|
|
651
657
|
|
|
652
658
|
write("src/index.js", `
|
|
653
659
|
export { Button } from "./components/ui/button.jsx";
|
|
654
|
-
export { default as Card } from "./components/ui/Card.jsx";
|
|
655
|
-
export { default as Input } from "./components/ui/Input.jsx";
|
|
656
|
-
export { default as FormField } from "./components/ui/FormField.jsx";
|
|
657
|
-
export { default as Table } from "./components/ui/Table.jsx";
|
|
658
|
-
export { default as Navbar } from "./components/ui/Navbar.jsx";
|
|
659
|
-
export { default as Sidebar } from "./components/ui/Sidebar.jsx";
|
|
660
|
-
export { default as Modal } from "./components/ui/Modal.jsx";
|
|
661
|
-
export { default as Tabs } from "./components/ui/Tabs.jsx";
|
|
662
|
-
export { ToastProvider, useToast } from "./components/ui/ToastProvider.jsx";
|
|
663
660
|
|
|
664
661
|
export { default as Login } from "./pages/auth/Login.jsx";
|
|
665
662
|
export { default as Register } from "./pages/auth/Register.jsx";
|
|
@@ -674,41 +671,31 @@ export { hashPassword, verifyPassword, getPasswordStrength, getPasswordStrengthL
|
|
|
674
671
|
write("src/App.jsx", `
|
|
675
672
|
import { useState } from "react";
|
|
676
673
|
import { Routes, Route, useNavigate } from "react-router-dom";
|
|
677
|
-
import {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
const columns = [
|
|
694
|
-
{ key: "name", label: "Student" },
|
|
695
|
-
{ key: "course", label: "Course" },
|
|
696
|
-
{ key: "status", label: "Status" }
|
|
697
|
-
];
|
|
698
|
-
|
|
699
|
-
const data = [
|
|
674
|
+
import { Button } from "@/components/ui/button";
|
|
675
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
676
|
+
import { Input } from "@/components/ui/input";
|
|
677
|
+
import { Label } from "@/components/ui/label";
|
|
678
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
|
679
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
680
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
681
|
+
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
|
682
|
+
import { toast } from "sonner";
|
|
683
|
+
import { Menu } from "lucide-react";
|
|
684
|
+
import { ApiClient } from "./utils/api.js";
|
|
685
|
+
import Login from "./pages/auth/Login.jsx";
|
|
686
|
+
import Register from "./pages/auth/Register.jsx";
|
|
687
|
+
|
|
688
|
+
const enrollmentData = [
|
|
700
689
|
{ name: "Alex", course: "Web Design Fundamentals", status: "Enrolled" },
|
|
701
690
|
{ name: "Jordan", course: "Advanced Web App Design", status: "Waitlisted" },
|
|
702
691
|
{ name: "Taylor", course: "eSports Strategy", status: "Enrolled" }
|
|
703
692
|
];
|
|
704
693
|
|
|
705
694
|
function Dashboard() {
|
|
706
|
-
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
707
695
|
const [modalOpen, setModalOpen] = useState(false);
|
|
708
696
|
const [posts, setPosts] = useState([]);
|
|
709
697
|
const [loadingPosts, setLoadingPosts] = useState(false);
|
|
710
698
|
const [postsError, setPostsError] = useState("");
|
|
711
|
-
const toast = useToast();
|
|
712
699
|
const navigate = useNavigate();
|
|
713
700
|
const client = new ApiClient("https://jsonplaceholder.typicode.com");
|
|
714
701
|
|
|
@@ -724,129 +711,183 @@ function Dashboard() {
|
|
|
724
711
|
setLoadingPosts(false);
|
|
725
712
|
};
|
|
726
713
|
|
|
727
|
-
const
|
|
728
|
-
{ label: "
|
|
729
|
-
{ label: "
|
|
730
|
-
{ label: "
|
|
714
|
+
const navLinks = [
|
|
715
|
+
{ label: "Home", href: "/" },
|
|
716
|
+
{ label: "Login", href: "/login" },
|
|
717
|
+
{ label: "Register", href: "/register" }
|
|
731
718
|
];
|
|
732
719
|
|
|
733
720
|
return (
|
|
734
721
|
<div className="flex h-screen overflow-hidden">
|
|
735
722
|
|
|
736
|
-
{/* Sidebar */}
|
|
737
|
-
<
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
723
|
+
{/* Sidebar (desktop) */}
|
|
724
|
+
<aside className="hidden md:flex flex-col w-64 border-r bg-white">
|
|
725
|
+
<div className="p-4 border-b font-semibold">Menu</div>
|
|
726
|
+
<nav className="p-4 space-y-1">
|
|
727
|
+
{navLinks.map((l) => (
|
|
728
|
+
<a key={l.label} href={l.href} className="block px-2 py-2 rounded hover:bg-gray-100 text-sm">
|
|
729
|
+
{l.label}
|
|
730
|
+
</a>
|
|
731
|
+
))}
|
|
732
|
+
</nav>
|
|
733
|
+
</aside>
|
|
746
734
|
|
|
747
735
|
{/* Main content */}
|
|
748
736
|
<div className="flex-1 flex flex-col">
|
|
749
737
|
|
|
750
738
|
{/* Navbar */}
|
|
751
|
-
<
|
|
739
|
+
<nav className="bg-white border-b px-4 py-3 flex items-center justify-between">
|
|
740
|
+
{/* Mobile sidebar trigger */}
|
|
741
|
+
<Sheet>
|
|
742
|
+
<SheetTrigger asChild>
|
|
743
|
+
<Button variant="ghost" size="icon" className="md:hidden">
|
|
744
|
+
<Menu />
|
|
745
|
+
</Button>
|
|
746
|
+
</SheetTrigger>
|
|
747
|
+
<SheetContent side="left" className="w-64 p-0">
|
|
748
|
+
<div className="p-4 border-b font-semibold">Menu</div>
|
|
749
|
+
<nav className="p-4 space-y-1">
|
|
750
|
+
{navLinks.map((l) => (
|
|
751
|
+
<a key={l.label} href={l.href} className="block px-2 py-2 rounded hover:bg-gray-100 text-sm">
|
|
752
|
+
{l.label}
|
|
753
|
+
</a>
|
|
754
|
+
))}
|
|
755
|
+
</nav>
|
|
756
|
+
</SheetContent>
|
|
757
|
+
</Sheet>
|
|
758
|
+
<h1 className="text-xl font-bold">BDPA React Scaffold and Demo</h1>
|
|
759
|
+
<div />
|
|
760
|
+
</nav>
|
|
752
761
|
|
|
753
762
|
{/* Page content */}
|
|
754
763
|
<div className="p-6 space-y-6 overflow-auto">
|
|
755
764
|
|
|
756
765
|
{/* BDPA Logo Section */}
|
|
757
766
|
<Card className="text-center">
|
|
758
|
-
<
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
767
|
+
<CardContent className="pt-6">
|
|
768
|
+
<img
|
|
769
|
+
src="/BDPA_edited.png"
|
|
770
|
+
alt="BDPA Logo"
|
|
771
|
+
className="h-32 mx-auto mb-4"
|
|
772
|
+
/>
|
|
773
|
+
<h1 className="text-3xl font-bold text-blue-600">Welcome to BDPA</h1>
|
|
774
|
+
<p className="text-gray-600 mt-2">Black Data Professionals Association</p>
|
|
775
|
+
</CardContent>
|
|
765
776
|
</Card>
|
|
766
777
|
|
|
767
|
-
|
|
778
|
+
{/* Tabs */}
|
|
779
|
+
<Tabs defaultValue="overview">
|
|
780
|
+
<TabsList>
|
|
781
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
782
|
+
<TabsTrigger value="components">Components</TabsTrigger>
|
|
783
|
+
<TabsTrigger value="auth">Auth</TabsTrigger>
|
|
784
|
+
</TabsList>
|
|
785
|
+
<TabsContent value="overview"><p className="mt-2 text-sm">Welcome to the BDPA React Scaffold and Demo.</p></TabsContent>
|
|
786
|
+
<TabsContent value="components"><p className="mt-2 text-sm">Buttons, Cards, Inputs, Tables, and more.</p></TabsContent>
|
|
787
|
+
<TabsContent value="auth"><p className="mt-2 text-sm">Login + Registration pages included.</p></TabsContent>
|
|
788
|
+
</Tabs>
|
|
768
789
|
|
|
769
790
|
<div className="grid md:grid-cols-2 gap-6">
|
|
770
791
|
|
|
771
792
|
{/* Form/Card example */}
|
|
772
793
|
<Card>
|
|
773
|
-
<
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
<Input placeholder="e.g. Alex Johnson" />
|
|
778
|
-
</
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
<Input type="email" placeholder="student@example.com" />
|
|
782
|
-
</
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
<Input placeholder="Web Design Fundamentals" />
|
|
786
|
-
</
|
|
787
|
-
|
|
794
|
+
<CardHeader><CardTitle>Sample Form</CardTitle></CardHeader>
|
|
795
|
+
<CardContent className="space-y-4">
|
|
796
|
+
<div className="space-y-1">
|
|
797
|
+
<Label htmlFor="s-name">Student Name</Label>
|
|
798
|
+
<Input id="s-name" placeholder="e.g. Alex Johnson" />
|
|
799
|
+
</div>
|
|
800
|
+
<div className="space-y-1">
|
|
801
|
+
<Label htmlFor="s-email">Email</Label>
|
|
802
|
+
<Input id="s-email" type="email" placeholder="student@example.com" />
|
|
803
|
+
</div>
|
|
804
|
+
<div className="space-y-1">
|
|
805
|
+
<Label htmlFor="s-course">Course</Label>
|
|
806
|
+
<Input id="s-course" placeholder="Web Design Fundamentals" />
|
|
807
|
+
</div>
|
|
788
808
|
<div className="flex gap-2 pt-2">
|
|
789
809
|
<Button>Save</Button>
|
|
790
810
|
<Button variant="secondary">Cancel</Button>
|
|
791
811
|
</div>
|
|
792
|
-
</
|
|
812
|
+
</CardContent>
|
|
793
813
|
</Card>
|
|
794
814
|
|
|
795
815
|
{/* Table example */}
|
|
796
816
|
<Card>
|
|
797
|
-
<
|
|
798
|
-
<
|
|
817
|
+
<CardHeader><CardTitle>Enrollment Overview</CardTitle></CardHeader>
|
|
818
|
+
<CardContent>
|
|
819
|
+
<Table>
|
|
820
|
+
<TableHeader>
|
|
821
|
+
<TableRow>
|
|
822
|
+
<TableHead>Student</TableHead>
|
|
823
|
+
<TableHead>Course</TableHead>
|
|
824
|
+
<TableHead>Status</TableHead>
|
|
825
|
+
</TableRow>
|
|
826
|
+
</TableHeader>
|
|
827
|
+
<TableBody>
|
|
828
|
+
{enrollmentData.map((row) => (
|
|
829
|
+
<TableRow key={row.name}>
|
|
830
|
+
<TableCell>{row.name}</TableCell>
|
|
831
|
+
<TableCell>{row.course}</TableCell>
|
|
832
|
+
<TableCell>{row.status}</TableCell>
|
|
833
|
+
</TableRow>
|
|
834
|
+
))}
|
|
835
|
+
</TableBody>
|
|
836
|
+
</Table>
|
|
837
|
+
</CardContent>
|
|
799
838
|
</Card>
|
|
800
839
|
</div>
|
|
801
840
|
|
|
802
|
-
{/*
|
|
841
|
+
{/* Button Variants */}
|
|
803
842
|
<Card>
|
|
804
|
-
<
|
|
805
|
-
<
|
|
843
|
+
<CardHeader><CardTitle>Button Variants</CardTitle></CardHeader>
|
|
844
|
+
<CardContent className="flex flex-wrap gap-3">
|
|
806
845
|
<Button>Primary</Button>
|
|
807
846
|
<Button variant="secondary">Secondary</Button>
|
|
808
847
|
<Button variant="destructive">Danger</Button>
|
|
809
848
|
<Button variant="outline">Outline</Button>
|
|
810
|
-
</
|
|
849
|
+
</CardContent>
|
|
811
850
|
</Card>
|
|
812
851
|
|
|
813
852
|
{/* Live API Demo */}
|
|
814
853
|
<Card>
|
|
815
|
-
<
|
|
816
|
-
<
|
|
817
|
-
<
|
|
818
|
-
{loadingPosts
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
<span className="text-sm text-red-600">{postsError}</span>
|
|
854
|
+
<CardHeader><CardTitle>Live API Demo (JSONPlaceholder)</CardTitle></CardHeader>
|
|
855
|
+
<CardContent>
|
|
856
|
+
<div className="flex items-center gap-3 mb-3">
|
|
857
|
+
<Button onClick={fetchPosts} disabled={loadingPosts}>
|
|
858
|
+
{loadingPosts ? "Loading..." : "Fetch Posts"}
|
|
859
|
+
</Button>
|
|
860
|
+
{postsError && <span className="text-sm text-red-600">{postsError}</span>}
|
|
861
|
+
</div>
|
|
862
|
+
{posts.length > 0 && (
|
|
863
|
+
<ul className="list-disc pl-6 space-y-1">
|
|
864
|
+
{posts.map((p) => (
|
|
865
|
+
<li key={p.id} className="text-sm">
|
|
866
|
+
<span className="font-medium">#{p.id}</span> {p.title}
|
|
867
|
+
</li>
|
|
868
|
+
))}
|
|
869
|
+
</ul>
|
|
822
870
|
)}
|
|
823
|
-
</
|
|
824
|
-
{posts.length > 0 && (
|
|
825
|
-
<ul className="list-disc pl-6 space-y-1">
|
|
826
|
-
{posts.map((p) => (
|
|
827
|
-
<li key={p.id} className="text-sm">
|
|
828
|
-
<span className="font-medium">#{p.id}</span> {p.title}
|
|
829
|
-
</li>
|
|
830
|
-
))}
|
|
831
|
-
</ul>
|
|
832
|
-
)}
|
|
871
|
+
</CardContent>
|
|
833
872
|
</Card>
|
|
834
873
|
|
|
835
|
-
{/*
|
|
874
|
+
{/* Dialog + Toast */}
|
|
836
875
|
<div className="flex gap-4">
|
|
837
|
-
<Button onClick={() => setModalOpen(true)}>Open
|
|
838
|
-
<Button onClick={() => toast.
|
|
876
|
+
<Button onClick={() => setModalOpen(true)}>Open Dialog</Button>
|
|
877
|
+
<Button onClick={() => toast.success("This is a toast!")}>
|
|
839
878
|
Show Toast
|
|
840
879
|
</Button>
|
|
841
880
|
</div>
|
|
842
881
|
|
|
843
|
-
<
|
|
844
|
-
<
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
882
|
+
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
|
883
|
+
<DialogContent>
|
|
884
|
+
<DialogHeader>
|
|
885
|
+
<DialogTitle>Dialog Title</DialogTitle>
|
|
886
|
+
</DialogHeader>
|
|
887
|
+
<p className="text-sm">This is a dialog example using shadcn/ui.</p>
|
|
888
|
+
<Button className="mt-4" onClick={() => setModalOpen(false)}>Close</Button>
|
|
889
|
+
</DialogContent>
|
|
890
|
+
</Dialog>
|
|
850
891
|
|
|
851
892
|
</div>
|
|
852
893
|
</div>
|
|
@@ -887,287 +928,9 @@ export default function App() {
|
|
|
887
928
|
}
|
|
888
929
|
`);
|
|
889
930
|
|
|
890
|
-
//
|
|
891
|
-
//
|
|
892
|
-
//
|
|
893
|
-
|
|
894
|
-
// Note: Button component is provided by shadcn/ui (src/components/ui/button.jsx)
|
|
895
|
-
// Installed via: npx shadcn@latest add button
|
|
896
|
-
|
|
897
|
-
write("src/components/ui/Card.jsx", `
|
|
898
|
-
export default function Card({ children, className = "" }) {
|
|
899
|
-
return (
|
|
900
|
-
<div
|
|
901
|
-
className={\`bg-white shadow-sm rounded-lg p-4 md:p-6 border border-gray-200 \${className}\`}
|
|
902
|
-
>
|
|
903
|
-
{children}
|
|
904
|
-
</div>
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
`);
|
|
908
|
-
|
|
909
|
-
write("src/components/ui/Input.jsx", `
|
|
910
|
-
export default function Input({ label, className = "", ...props }) {
|
|
911
|
-
return (
|
|
912
|
-
<label className="flex flex-col gap-1 text-sm">
|
|
913
|
-
{label && (
|
|
914
|
-
<span className="font-medium text-gray-700">
|
|
915
|
-
{label}
|
|
916
|
-
</span>
|
|
917
|
-
)}
|
|
918
|
-
<input
|
|
919
|
-
className={\`border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none text-sm \${className}\`}
|
|
920
|
-
{...props}
|
|
921
|
-
/>
|
|
922
|
-
</label>
|
|
923
|
-
);
|
|
924
|
-
}
|
|
925
|
-
`);
|
|
926
|
-
|
|
927
|
-
write("src/components/ui/FormField.jsx", `
|
|
928
|
-
export default function FormField({ label, error, children, helperText }) {
|
|
929
|
-
return (
|
|
930
|
-
<div className="flex flex-col gap-1 text-sm">
|
|
931
|
-
{label && (
|
|
932
|
-
<label className="font-medium text-gray-700">
|
|
933
|
-
{label}
|
|
934
|
-
</label>
|
|
935
|
-
)}
|
|
936
|
-
|
|
937
|
-
{children}
|
|
938
|
-
|
|
939
|
-
{helperText && !error && (
|
|
940
|
-
<p className="text-xs text-gray-500">{helperText}</p>
|
|
941
|
-
)}
|
|
942
|
-
|
|
943
|
-
{error && (
|
|
944
|
-
<p className="text-xs text-red-600">
|
|
945
|
-
{error}
|
|
946
|
-
</p>
|
|
947
|
-
)}
|
|
948
|
-
</div>
|
|
949
|
-
);
|
|
950
|
-
}
|
|
951
|
-
`);
|
|
952
|
-
|
|
953
|
-
write("src/components/ui/Table.jsx", `
|
|
954
|
-
export default function Table({ columns, data }) {
|
|
955
|
-
return (
|
|
956
|
-
<div className="overflow-x-auto">
|
|
957
|
-
<table className="min-w-full border border-gray-200 bg-white rounded-lg overflow-hidden">
|
|
958
|
-
<thead className="bg-gray-100">
|
|
959
|
-
<tr>
|
|
960
|
-
{columns.map((col) => (
|
|
961
|
-
<th
|
|
962
|
-
key={col.key}
|
|
963
|
-
className="px-4 py-2 text-left text-xs font-semibold text-gray-700 border-b border-gray-200"
|
|
964
|
-
>
|
|
965
|
-
{col.label}
|
|
966
|
-
</th>
|
|
967
|
-
))}
|
|
968
|
-
</tr>
|
|
969
|
-
</thead>
|
|
970
|
-
|
|
971
|
-
<tbody>
|
|
972
|
-
{data.map((row, i) => (
|
|
973
|
-
<tr
|
|
974
|
-
key={i}
|
|
975
|
-
className={i % 2 === 0 ? "bg-white" : "bg-gray-50"}
|
|
976
|
-
>
|
|
977
|
-
{columns.map((col) => (
|
|
978
|
-
<td
|
|
979
|
-
key={col.key}
|
|
980
|
-
className="px-4 py-2 text-sm text-gray-800 border-b border-gray-100"
|
|
981
|
-
>
|
|
982
|
-
{row[col.key]}
|
|
983
|
-
</td>
|
|
984
|
-
))}
|
|
985
|
-
</tr>
|
|
986
|
-
))}
|
|
987
|
-
</tbody>
|
|
988
|
-
</table>
|
|
989
|
-
</div>
|
|
990
|
-
);
|
|
991
|
-
}
|
|
992
|
-
`);
|
|
993
|
-
|
|
994
|
-
write("src/components/ui/Navbar.jsx", `
|
|
995
|
-
import { Menu, ChevronDown } from "lucide-react";
|
|
996
|
-
import { useState } from "react";
|
|
997
|
-
|
|
998
|
-
export default function Navbar({ onMenuClick }) {
|
|
999
|
-
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
1000
|
-
|
|
1001
|
-
const dropdownItems = [
|
|
1002
|
-
{ label: "Profile", href: "#" },
|
|
1003
|
-
{ label: "Settings", href: "#" },
|
|
1004
|
-
{ label: "Help", href: "#" },
|
|
1005
|
-
{ label: "Feedback", href: "#" },
|
|
1006
|
-
{ label: "Logout", href: "#" }
|
|
1007
|
-
];
|
|
1008
|
-
|
|
1009
|
-
return (
|
|
1010
|
-
<nav className="bg-white border-b border-gray-200 px-4 py-3 flex items-center justify-between">
|
|
1011
|
-
<button className="md:hidden" onClick={onMenuClick}>
|
|
1012
|
-
<Menu />
|
|
1013
|
-
</button>
|
|
1014
|
-
<h1 className="text-xl font-bold">BDPA React Scaffold and Demo</h1>
|
|
1015
|
-
|
|
1016
|
-
{/* Dropdown Menu */}
|
|
1017
|
-
<div className="relative">
|
|
1018
|
-
<button
|
|
1019
|
-
onClick={() => setDropdownOpen(!dropdownOpen)}
|
|
1020
|
-
className="flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-100"
|
|
1021
|
-
>
|
|
1022
|
-
Menu
|
|
1023
|
-
<ChevronDown size={18} />
|
|
1024
|
-
</button>
|
|
1025
|
-
|
|
1026
|
-
{dropdownOpen && (
|
|
1027
|
-
<div className="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-lg shadow-lg z-50">
|
|
1028
|
-
{dropdownItems.map((item, index) => (
|
|
1029
|
-
<a
|
|
1030
|
-
key={index}
|
|
1031
|
-
href={item.href}
|
|
1032
|
-
className="block px-4 py-2 hover:bg-gray-100 first:rounded-t-lg last:rounded-b-lg"
|
|
1033
|
-
>
|
|
1034
|
-
{item.label}
|
|
1035
|
-
</a>
|
|
1036
|
-
))}
|
|
1037
|
-
</div>
|
|
1038
|
-
)}
|
|
1039
|
-
</div>
|
|
1040
|
-
</nav>
|
|
1041
|
-
);
|
|
1042
|
-
}
|
|
1043
|
-
`);
|
|
1044
|
-
|
|
1045
|
-
write("src/components/ui/Sidebar.jsx", `
|
|
1046
|
-
import { Link } from "react-router-dom";
|
|
1047
|
-
|
|
1048
|
-
export default function Sidebar({ open, onToggle, links }) {
|
|
1049
|
-
return (
|
|
1050
|
-
<div
|
|
1051
|
-
className={\`
|
|
1052
|
-
fixed md:static inset-y-0 left-0 z-40
|
|
1053
|
-
bg-white border-r border-gray-200
|
|
1054
|
-
h-full w-64 transform
|
|
1055
|
-
transition-transform duration-200
|
|
1056
|
-
\${open ? "translate-x-0" : "-translate-x-full md:translate-x-0"}
|
|
1057
|
-
\`}
|
|
1058
|
-
>
|
|
1059
|
-
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
|
|
1060
|
-
<h2 className="font-semibold">Menu</h2>
|
|
1061
|
-
<button className="md:hidden" onClick={onToggle}>✕</button>
|
|
1062
|
-
</div>
|
|
1063
|
-
|
|
1064
|
-
<ul className="p-4 space-y-2">
|
|
1065
|
-
{links.map((l) => (
|
|
1066
|
-
<li key={l.label}>
|
|
1067
|
-
<Link to={l.href} className="block px-2 py-2 rounded hover:bg-gray-100">
|
|
1068
|
-
{l.label}
|
|
1069
|
-
</Link>
|
|
1070
|
-
</li>
|
|
1071
|
-
))}
|
|
1072
|
-
</ul>
|
|
1073
|
-
</div>
|
|
1074
|
-
);
|
|
1075
|
-
}
|
|
1076
|
-
`);
|
|
1077
|
-
|
|
1078
|
-
write("src/components/ui/Modal.jsx", `
|
|
1079
|
-
export default function Modal({ open, onClose, children }) {
|
|
1080
|
-
if (!open) return null;
|
|
1081
|
-
|
|
1082
|
-
return (
|
|
1083
|
-
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
|
|
1084
|
-
<div className="bg-white rounded-lg shadow-lg p-6 w-full max-w-md relative">
|
|
1085
|
-
<button
|
|
1086
|
-
className="absolute top-3 right-3 text-gray-500 hover:text-gray-700"
|
|
1087
|
-
onClick={onClose}
|
|
1088
|
-
>
|
|
1089
|
-
✕
|
|
1090
|
-
</button>
|
|
1091
|
-
|
|
1092
|
-
{children}
|
|
1093
|
-
</div>
|
|
1094
|
-
</div>
|
|
1095
|
-
);
|
|
1096
|
-
}
|
|
1097
|
-
`);
|
|
1098
|
-
|
|
1099
|
-
write("src/components/ui/Tabs.jsx", `
|
|
1100
|
-
import { useState } from "react";
|
|
1101
|
-
|
|
1102
|
-
export default function Tabs({ tabs }) {
|
|
1103
|
-
const [active, setActive] = useState(0);
|
|
1104
|
-
|
|
1105
|
-
return (
|
|
1106
|
-
<div>
|
|
1107
|
-
<div className="flex gap-4 border-b border-gray-200">
|
|
1108
|
-
{tabs.map((t, i) => (
|
|
1109
|
-
<button
|
|
1110
|
-
key={i}
|
|
1111
|
-
onClick={() => setActive(i)}
|
|
1112
|
-
className={\`pb-2 text-sm font-medium \${active === i
|
|
1113
|
-
? "border-b-2 border-blue-600 text-blue-600"
|
|
1114
|
-
: "text-gray-600 hover:text-gray-800"
|
|
1115
|
-
}\`}
|
|
1116
|
-
>
|
|
1117
|
-
{t.label}
|
|
1118
|
-
</button>
|
|
1119
|
-
))}
|
|
1120
|
-
</div>
|
|
1121
|
-
|
|
1122
|
-
<div className="mt-4">{tabs[active].content}</div>
|
|
1123
|
-
</div>
|
|
1124
|
-
);
|
|
1125
|
-
}
|
|
1126
|
-
`);
|
|
1127
|
-
|
|
1128
|
-
write("src/components/ui/ToastProvider.jsx", `
|
|
1129
|
-
import { createContext, useContext, useState } from "react";
|
|
1130
|
-
|
|
1131
|
-
const ToastContext = createContext();
|
|
1132
|
-
|
|
1133
|
-
export function useToast() {
|
|
1134
|
-
return useContext(ToastContext);
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
export function ToastProvider({ children }) {
|
|
1138
|
-
const [toasts, setToasts] = useState([]);
|
|
1139
|
-
|
|
1140
|
-
const show = (message, type = "info") => {
|
|
1141
|
-
const id = Date.now();
|
|
1142
|
-
setToasts((t) => [...t, { id, message, type }]);
|
|
1143
|
-
setTimeout(() => {
|
|
1144
|
-
setToasts((t) => t.filter((toast) => toast.id !== id));
|
|
1145
|
-
}, 3000);
|
|
1146
|
-
};
|
|
1147
|
-
|
|
1148
|
-
return (
|
|
1149
|
-
<ToastContext.Provider value={{ show }}>
|
|
1150
|
-
{children}
|
|
1151
|
-
|
|
1152
|
-
<div className="fixed bottom-4 right-4 space-y-3 z-50">
|
|
1153
|
-
{toasts.map((t) => (
|
|
1154
|
-
<div
|
|
1155
|
-
key={t.id}
|
|
1156
|
-
className={\`px-4 py-2 rounded-md shadow text-white \${t.type === "success"
|
|
1157
|
-
? "bg-green-600"
|
|
1158
|
-
: t.type === "error"
|
|
1159
|
-
? "bg-red-600"
|
|
1160
|
-
: "bg-gray-800"
|
|
1161
|
-
}\`}
|
|
1162
|
-
>
|
|
1163
|
-
{t.message}
|
|
1164
|
-
</div>
|
|
1165
|
-
))}
|
|
1166
|
-
</div>
|
|
1167
|
-
</ToastContext.Provider>
|
|
1168
|
-
);
|
|
1169
|
-
}
|
|
1170
|
-
`);
|
|
931
|
+
// Note: All UI components (Card, Input, Form, Table, Navbar, Tabs, Toast, etc.)
|
|
932
|
+
// are provided by shadcn/ui — installed via installShadcn() below.
|
|
933
|
+
// No manual component files are written here.
|
|
1171
934
|
|
|
1172
935
|
// -------------------------------
|
|
1173
936
|
// Auth Components
|
|
@@ -1175,28 +938,36 @@ export function ToastProvider({ children }) {
|
|
|
1175
938
|
|
|
1176
939
|
write("src/pages/auth/Login.jsx", `
|
|
1177
940
|
import { Button } from "@/components/ui/button";
|
|
1178
|
-
import Input from "
|
|
1179
|
-
import
|
|
941
|
+
import { Input } from "@/components/ui/input";
|
|
942
|
+
import { Label } from "@/components/ui/label";
|
|
943
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
1180
944
|
|
|
1181
945
|
export default function Login({ onSubmit }) {
|
|
1182
946
|
return (
|
|
1183
947
|
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
|
1184
|
-
<Card className="w-full max-w-sm
|
|
1185
|
-
<
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
<
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
948
|
+
<Card className="w-full max-w-sm">
|
|
949
|
+
<CardHeader>
|
|
950
|
+
<CardTitle>Sign In</CardTitle>
|
|
951
|
+
</CardHeader>
|
|
952
|
+
<CardContent className="space-y-4">
|
|
953
|
+
<div className="space-y-1">
|
|
954
|
+
<Label htmlFor="email">Email</Label>
|
|
955
|
+
<Input id="email" type="email" placeholder="you@example.com" />
|
|
956
|
+
</div>
|
|
957
|
+
<div className="space-y-1">
|
|
958
|
+
<Label htmlFor="password">Password</Label>
|
|
959
|
+
<Input id="password" type="password" placeholder="••••••••" />
|
|
960
|
+
</div>
|
|
961
|
+
<Button className="w-full" onClick={onSubmit}>
|
|
962
|
+
Sign In
|
|
963
|
+
</Button>
|
|
964
|
+
<p className="text-sm text-center text-gray-600">
|
|
965
|
+
Don’t have an account?{" "}
|
|
966
|
+
<a href="/register" className="text-blue-600 hover:underline">
|
|
967
|
+
Create one
|
|
968
|
+
</a>
|
|
969
|
+
</p>
|
|
970
|
+
</CardContent>
|
|
1200
971
|
</Card>
|
|
1201
972
|
</div>
|
|
1202
973
|
);
|
|
@@ -1204,29 +975,40 @@ export default function Login({ onSubmit }) {
|
|
|
1204
975
|
|
|
1205
976
|
write("src/pages/auth/Register.jsx", `
|
|
1206
977
|
import { Button } from "@/components/ui/button";
|
|
1207
|
-
import Input from "
|
|
1208
|
-
import
|
|
978
|
+
import { Input } from "@/components/ui/input";
|
|
979
|
+
import { Label } from "@/components/ui/label";
|
|
980
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
1209
981
|
|
|
1210
982
|
export default function Register({ onSubmit }) {
|
|
1211
983
|
return (
|
|
1212
984
|
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
|
1213
|
-
<Card className="w-full max-w-sm
|
|
1214
|
-
<
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
<
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
<
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
985
|
+
<Card className="w-full max-w-sm">
|
|
986
|
+
<CardHeader>
|
|
987
|
+
<CardTitle>Create Account</CardTitle>
|
|
988
|
+
</CardHeader>
|
|
989
|
+
<CardContent className="space-y-4">
|
|
990
|
+
<div className="space-y-1">
|
|
991
|
+
<Label htmlFor="name">Full Name</Label>
|
|
992
|
+
<Input id="name" placeholder="Your Name" />
|
|
993
|
+
</div>
|
|
994
|
+
<div className="space-y-1">
|
|
995
|
+
<Label htmlFor="email">Email</Label>
|
|
996
|
+
<Input id="email" type="email" placeholder="you@example.com" />
|
|
997
|
+
</div>
|
|
998
|
+
<div className="space-y-1">
|
|
999
|
+
<Label htmlFor="password">Password</Label>
|
|
1000
|
+
<Input id="password" type="password" placeholder="••••••••" />
|
|
1001
|
+
</div>
|
|
1002
|
+
<Button className="w-full" onClick={onSubmit}>
|
|
1003
|
+
Register
|
|
1004
|
+
</Button>
|
|
1005
|
+
<p className="text-sm text-center text-gray-600">
|
|
1006
|
+
Already have an account?{" "}
|
|
1007
|
+
<a href="/login" className="text-blue-600 hover:underline">
|
|
1008
|
+
Sign in
|
|
1009
|
+
</a>
|
|
1010
|
+
</p>
|
|
1011
|
+
</CardContent>
|
|
1230
1012
|
</Card>
|
|
1231
1013
|
</div>
|
|
1232
1014
|
);
|
|
@@ -1394,18 +1176,18 @@ console.log("\nNext steps:");
|
|
|
1394
1176
|
console.log(" 1. npm run dev");
|
|
1395
1177
|
console.log(" 3. Open http://localhost:3000 in your browser\n");
|
|
1396
1178
|
|
|
1397
|
-
// Create
|
|
1398
|
-
fs.mkdirSync(path.join(BASE_DIR, "
|
|
1399
|
-
console.log("✔ Created:
|
|
1179
|
+
// Create public folder structure
|
|
1180
|
+
fs.mkdirSync(path.join(BASE_DIR, "public"), { recursive: true });
|
|
1181
|
+
console.log("✔ Created: public");
|
|
1400
1182
|
|
|
1401
|
-
// Copy BDPA logo image
|
|
1402
|
-
const bdpaImagePath = path.join(__dirname, "BDPA_edited.
|
|
1403
|
-
const bdpaDestPath = path.join(BASE_DIR, "
|
|
1183
|
+
// Copy BDPA logo image to public (used as favicon)
|
|
1184
|
+
const bdpaImagePath = path.join(__dirname, "BDPA_edited.png");
|
|
1185
|
+
const bdpaDestPath = path.join(BASE_DIR, "public/BDPA_edited.png");
|
|
1404
1186
|
if (fs.existsSync(bdpaImagePath)) {
|
|
1405
1187
|
fs.copyFileSync(bdpaImagePath, bdpaDestPath);
|
|
1406
|
-
console.log("✔ Copied:
|
|
1188
|
+
console.log("✔ Copied: public/BDPA_edited.png\n");
|
|
1407
1189
|
} else {
|
|
1408
|
-
console.log("⚠ Warning: BDPA_edited.
|
|
1190
|
+
console.log("⚠ Warning: BDPA_edited.png not found in package\n");
|
|
1409
1191
|
}
|
|
1410
1192
|
|
|
1411
1193
|
if (doInstall) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-bdpa-react-scaffold",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Scaffold a React + Tailwind UI library demo via Vite.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-bdpa-react-scaffold": "create-ui-lib.js"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"create-ui-lib.js",
|
|
10
10
|
"README.md",
|
|
11
|
-
"BDPA_edited.
|
|
11
|
+
"BDPA_edited.png"
|
|
12
12
|
],
|
|
13
13
|
"keywords": [
|
|
14
14
|
"create",
|
package/BDPA_edited.avif
DELETED
|
Binary file
|