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