@appcorp/shadcn 1.1.89 → 1.1.91

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.
@@ -72,8 +72,8 @@ var EnhancedDropzone = function (_a) {
72
72
  var _f = (0, react_1.useState)(false), resizeDialogOpen = _f[0], setResizeDialogOpen = _f[1];
73
73
  var _g = (0, react_1.useState)(null), pendingFile = _g[0], setPendingFile = _g[1];
74
74
  var _h = (0, react_1.useState)(""), pendingFileUrl = _h[0], setPendingFileUrl = _h[1];
75
- // Create object URLs for local files synchronously to avoid loading state
76
- var createObjectURLs = (0, react_1.useCallback)(function () {
75
+ // Create and manage object URLs for local files
76
+ (0, react_1.useEffect)(function () {
77
77
  var map = localPreviewsRef.current;
78
78
  // Create URLs for new files
79
79
  localFiles.forEach(function (file) {
@@ -97,8 +97,6 @@ var EnhancedDropzone = function (_a) {
97
97
  }
98
98
  });
99
99
  }, [localFiles]);
100
- // Run synchronously on every render to avoid "loading" flash
101
- createObjectURLs();
102
100
  // Cleanup on unmount
103
101
  (0, react_1.useEffect)(function () {
104
102
  var map = localPreviewsRef.current;
@@ -111,15 +109,38 @@ var EnhancedDropzone = function (_a) {
111
109
  }, []);
112
110
  // Cleanup pending file URL when dialog closes
113
111
  (0, react_1.useEffect)(function () {
114
- if (!resizeDialogOpen && pendingFileUrl) {
115
- URL.revokeObjectURL(pendingFileUrl);
116
- setPendingFileUrl("");
117
- setPendingFile(null);
112
+ return function () {
113
+ if (pendingFileUrl) {
114
+ URL.revokeObjectURL(pendingFileUrl);
115
+ }
116
+ };
117
+ }, [pendingFileUrl]);
118
+ // Check if accept includes image types
119
+ var isImageOnly = react_1.default.useMemo(function () {
120
+ if (!accept)
121
+ return false;
122
+ var acceptTypes = Array.isArray(accept)
123
+ ? accept
124
+ : Object.keys(accept || {});
125
+ return acceptTypes.some(function (type) { return typeof type === "string" && type.toLowerCase().includes("image"); });
126
+ }, [accept]);
127
+ // Convert accept array to react-dropzone format if needed
128
+ var normalizedAccept = react_1.default.useMemo(function () {
129
+ if (!accept)
130
+ return undefined;
131
+ if (Array.isArray(accept)) {
132
+ // Convert string array like ['image/*'] to object format { 'image/*': [] }
133
+ return accept.reduce(function (acc, type) {
134
+ acc[type] = [];
135
+ return acc;
136
+ }, {});
118
137
  }
119
- }, [resizeDialogOpen, pendingFileUrl]);
120
- var dropzoneOptions = {
138
+ // Already in object format
121
139
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
- accept: (Array.isArray(accept) ? undefined : accept) || undefined,
140
+ return accept;
141
+ }, [accept]);
142
+ var dropzoneOptions = {
143
+ accept: normalizedAccept,
123
144
  maxFiles: maxFiles,
124
145
  maxSize: maxSize,
125
146
  minSize: minSize,
@@ -169,6 +190,9 @@ var EnhancedDropzone = function (_a) {
169
190
  // Handle resize dialog close
170
191
  var handleResizeDialogClose = (0, react_1.useCallback)(function () {
171
192
  setResizeDialogOpen(false);
193
+ // Cleanup will happen via the pendingFileUrl useEffect
194
+ setPendingFileUrl("");
195
+ setPendingFile(null);
172
196
  }, []);
173
197
  // Get all preview URLs (remote + local)
174
198
  var allPreviews = __spreadArray(__spreadArray([], value.map(function (url) { return ({ type: "remote", url: url, index: 0 }); }), true), localFiles.map(function (file, index) { return ({
@@ -176,11 +200,36 @@ var EnhancedDropzone = function (_a) {
176
200
  url: localPreviewsRef.current.get(file) || "",
177
201
  index: index,
178
202
  }); }), true);
179
- return (react_1.default.createElement("div", { className: (0, utils_1.cn)("w-full", className), "data-testid": testIdDropzone },
203
+ // Format bytes to human-readable size
204
+ var formatBytes = function (bytes) {
205
+ if (bytes === 0)
206
+ return "0 B";
207
+ var k = 1024;
208
+ var sizes = ["B", "KB", "MB", "GB"];
209
+ var i = Math.floor(Math.log(bytes) / Math.log(k));
210
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
211
+ };
212
+ // Build constraint text for the note
213
+ var getConstraintText = function () {
214
+ var constraints = [];
215
+ if (maxFiles && maxFiles > 1) {
216
+ constraints.push("Up to ".concat(maxFiles, " files"));
217
+ }
218
+ if (minSize || maxSize) {
219
+ var sizes = [];
220
+ if (minSize)
221
+ sizes.push("min ".concat(formatBytes(minSize)));
222
+ if (maxSize)
223
+ sizes.push("max ".concat(formatBytes(maxSize)));
224
+ constraints.push("".concat(sizes.join(", ")));
225
+ }
226
+ return constraints.join(" • ");
227
+ };
228
+ return (react_1.default.createElement("div", { className: (0, utils_1.cn)("w-full", className), "data-slot": "enhanced-dropzone", "data-testid": testIdDropzone },
180
229
  label && (react_1.default.createElement("label", { className: "mb-2 block text-sm font-medium" }, label)),
181
- react_1.default.createElement("div", __assign({}, getRootProps(), { className: (0, utils_1.cn)("relative w-full rounded-md border border-dashed p-6 text-center min-h-[280px] flex items-center justify-center", isDragActive && "ring-2 ring-ring ring-offset-2", disabled && "opacity-60 pointer-events-none") }),
230
+ react_1.default.createElement("div", __assign({}, getRootProps(), { className: (0, utils_1.cn)("relative w-full rounded-md border border-dashed p-6 text-center min-h-70 flex items-center justify-center", isDragActive && "ring-2 ring-ring ring-offset-2", disabled && "opacity-60 pointer-events-none") }),
182
231
  react_1.default.createElement("input", __assign({}, getInputProps(), { id: id, "data-testid": testIdInput })),
183
- allPreviews.length > 0 ? (react_1.default.createElement("div", { className: "flex flex-col items-center w-full" },
232
+ isImageOnly && allPreviews.length > 0 ? (react_1.default.createElement("div", { className: "flex flex-col items-center w-full" },
184
233
  react_1.default.createElement("div", { className: "relative w-full max-w-md" },
185
234
  react_1.default.createElement(carousel_1.Carousel, { className: "w-full" },
186
235
  react_1.default.createElement(carousel_1.CarouselPrevious, { type: "button", onClick: function (e) { return e.stopPropagation(); }, onPointerDown: function (e) { return e.stopPropagation(); }, onMouseDown: function (e) { return e.stopPropagation(); }, "data-testid": testIdPrev }),
@@ -204,6 +253,31 @@ var EnhancedDropzone = function (_a) {
204
253
  " image",
205
254
  allPreviews.length !== 1 ? "s" : "",
206
255
  " ",
256
+ "selected"))) : !isImageOnly && allPreviews.length > 0 ? (react_1.default.createElement("div", { className: "flex flex-col items-center w-full gap-4" },
257
+ react_1.default.createElement("div", { className: "w-full max-w-md" },
258
+ react_1.default.createElement("div", { className: "space-y-2" }, allPreviews.map(function (preview, idx) {
259
+ var _a;
260
+ return (react_1.default.createElement("div", { key: "".concat(preview.type, "-").concat(preview.url, "-").concat(idx), "data-testid": "".concat(testIdPreviewPrefix !== null && testIdPreviewPrefix !== void 0 ? testIdPreviewPrefix : "dropzone-preview", "-").concat(idx), className: "flex items-center justify-between gap-3 p-3 rounded-lg border bg-muted/50" },
261
+ react_1.default.createElement("span", { className: "text-sm truncate flex-1", "data-testid": "".concat(testIdImagePrefix !== null && testIdImagePrefix !== void 0 ? testIdImagePrefix : "dropzone-image", "-").concat(idx) }, preview.type === "remote"
262
+ ? new URL(preview.url).pathname.split("/").pop() ||
263
+ "File"
264
+ : ((_a = localFiles[preview.index]) === null || _a === void 0 ? void 0 : _a.name) || "File"),
265
+ react_1.default.createElement(button_1.Button, { type: "button", size: "icon", variant: "ghost", onClick: function (e) {
266
+ e.stopPropagation();
267
+ if (preview.type === "remote") {
268
+ handleRemoveRemote(preview.url);
269
+ }
270
+ else {
271
+ handleRemoveLocal(preview.index);
272
+ }
273
+ }, className: "h-8 w-8 shrink-0", "aria-label": "Remove file", "data-testid": "".concat(testIdRemovePrefix !== null && testIdRemovePrefix !== void 0 ? testIdRemovePrefix : "dropzone-remove", "-").concat(idx) },
274
+ react_1.default.createElement(lucide_react_1.XIcon, { className: "h-4 w-4" }))));
275
+ }))),
276
+ react_1.default.createElement("p", { className: "text-sm font-medium text-muted-foreground", "data-testid": testIdCount },
277
+ allPreviews.length,
278
+ " file",
279
+ allPreviews.length !== 1 ? "s" : "",
280
+ " ",
207
281
  "selected"))) : (react_1.default.createElement("div", { className: "flex flex-col items-center justify-center gap-2" },
208
282
  react_1.default.createElement("div", { className: "flex h-12 w-12 items-center justify-center rounded-lg bg-muted text-muted-foreground", "data-testid": testIdEmptyIcon },
209
283
  react_1.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "lucide lucide-image" },
@@ -211,14 +285,11 @@ var EnhancedDropzone = function (_a) {
211
285
  react_1.default.createElement("circle", { cx: "9", cy: "9", r: "2" }),
212
286
  react_1.default.createElement("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" }))),
213
287
  react_1.default.createElement("div", { className: "space-y-1 text-center" },
214
- react_1.default.createElement("p", { className: "font-medium text-sm", "data-testid": testIdEmptyTitle },
215
- "Upload ",
216
- maxFiles === 1 ? "an image" : "images"),
288
+ react_1.default.createElement("p", { className: "font-medium text-sm", "data-testid": testIdEmptyTitle }, maxFiles === 1
289
+ ? "Upload ".concat(isImageOnly ? "an image" : "a file")
290
+ : "Upload ".concat(isImageOnly ? "images" : "files")),
217
291
  react_1.default.createElement("p", { className: "text-muted-foreground text-xs", "data-testid": testIdEmptySubtitle }, "Drag and drop or click to browse"),
218
- maxFiles > 1 && (react_1.default.createElement("p", { className: "text-muted-foreground text-xs", "data-testid": testIdEmptyNote },
219
- "Up to ",
220
- maxFiles,
221
- " files")))))),
292
+ (maxFiles > 1 || minSize || maxSize) && (react_1.default.createElement("p", { className: "text-muted-foreground text-xs", "data-testid": testIdEmptyNote }, getConstraintText())))))),
222
293
  (error || info) && (react_1.default.createElement("div", { className: "mt-2", "data-testid": testIdMessage }, error ? (react_1.default.createElement("p", { className: "text-xs text-destructive" }, error)) : info ? (react_1.default.createElement("p", { className: "text-xs text-blue-600 dark:text-blue-400" }, info)) : null)),
223
294
  resize && pendingFile && (react_1.default.createElement(image_resize_dialog_1.ImageResizeDialog, { open: resizeDialogOpen, onClose: handleResizeDialogClose, imageUrl: pendingFileUrl, onCropComplete: handleCropComplete, fileName: pendingFile.name }))));
224
295
  };
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import * as ResizablePrimitive from "react-resizable-panels";
3
3
  declare const ResizablePanelGroup: ({ className, ...props }: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => React.JSX.Element;
4
- declare const ResizablePanel: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLElement | HTMLButtonElement | HTMLDivElement | HTMLFormElement | HTMLInputElement | HTMLObjectElement | HTMLSlotElement | HTMLStyleElement | HTMLTitleElement | HTMLSpanElement | HTMLAnchorElement | HTMLAreaElement | HTMLAudioElement | HTMLBaseElement | HTMLQuoteElement | HTMLBodyElement | HTMLBRElement | HTMLCanvasElement | HTMLTableColElement | HTMLDataElement | HTMLDataListElement | HTMLModElement | HTMLDetailsElement | HTMLDialogElement | HTMLDListElement | HTMLEmbedElement | HTMLFieldSetElement | HTMLHeadingElement | HTMLHeadElement | HTMLHRElement | HTMLHtmlElement | HTMLIFrameElement | HTMLImageElement | HTMLLabelElement | HTMLLegendElement | HTMLLIElement | HTMLLinkElement | HTMLMapElement | HTMLMetaElement | HTMLMeterElement | HTMLOListElement | HTMLOptGroupElement | HTMLOptionElement | HTMLOutputElement | HTMLParagraphElement | HTMLPreElement | HTMLProgressElement | HTMLScriptElement | HTMLSelectElement | HTMLSourceElement | HTMLTableElement | HTMLTemplateElement | HTMLTableSectionElement | HTMLTableCellElement | HTMLTextAreaElement | HTMLTimeElement | HTMLTableRowElement | HTMLTrackElement | HTMLUListElement | HTMLVideoElement | HTMLTableCaptionElement | HTMLMenuElement | HTMLPictureElement>, "id" | "onResize"> & {
4
+ declare const ResizablePanel: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLElement | HTMLButtonElement | HTMLObjectElement | HTMLSlotElement | HTMLStyleElement | HTMLTitleElement | HTMLInputElement | HTMLSpanElement | HTMLAnchorElement | HTMLAreaElement | HTMLAudioElement | HTMLBaseElement | HTMLQuoteElement | HTMLBodyElement | HTMLBRElement | HTMLCanvasElement | HTMLTableColElement | HTMLDataElement | HTMLDataListElement | HTMLModElement | HTMLDetailsElement | HTMLDialogElement | HTMLDivElement | HTMLDListElement | HTMLEmbedElement | HTMLFieldSetElement | HTMLFormElement | HTMLHeadingElement | HTMLHeadElement | HTMLHRElement | HTMLHtmlElement | HTMLIFrameElement | HTMLImageElement | HTMLLabelElement | HTMLLegendElement | HTMLLIElement | HTMLLinkElement | HTMLMapElement | HTMLMetaElement | HTMLMeterElement | HTMLOListElement | HTMLOptGroupElement | HTMLOptionElement | HTMLOutputElement | HTMLParagraphElement | HTMLPreElement | HTMLProgressElement | HTMLScriptElement | HTMLSelectElement | HTMLSourceElement | HTMLTableElement | HTMLTemplateElement | HTMLTableSectionElement | HTMLTableCellElement | HTMLTextAreaElement | HTMLTimeElement | HTMLTableRowElement | HTMLTrackElement | HTMLUListElement | HTMLVideoElement | HTMLTableCaptionElement | HTMLMenuElement | HTMLPictureElement>, "id" | "onResize"> & {
5
5
  className?: string;
6
6
  collapsedSize?: number | undefined;
7
7
  collapsible?: boolean | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appcorp/shadcn",
3
- "version": "1.1.89",
3
+ "version": "1.1.91",
4
4
  "scripts": {
5
5
  "build:next": "next build",
6
6
  "build:storybook": "mv ../.pnp.cjs ../.pnp.cjs.bak 2>/dev/null || true && storybook build -c .storybook -o .out && mv ../.pnp.cjs.bak ../.pnp.cjs 2>/dev/null || true",
@@ -0,0 +1,33 @@
1
+ export interface DashboardMockupProps {
2
+ domain: string;
3
+ enrollmentHeading: string;
4
+ enrollmentList: {
5
+ name: string;
6
+ class: string;
7
+ status: string;
8
+ }[];
9
+ items: {
10
+ icon: React.ElementType;
11
+ label: string;
12
+ active: boolean;
13
+ }[];
14
+ schoolName: string;
15
+ stats: {
16
+ label: string;
17
+ value: string;
18
+ color: string;
19
+ }[];
20
+ }
21
+ export declare const dashboardMockupMenuList: DashboardMockupProps;
22
+ export interface FeatureCardsProps {
23
+ items: {
24
+ icon: React.ElementType;
25
+ title: string;
26
+ description: string;
27
+ highlights: string[];
28
+ href: string;
29
+ accent: string;
30
+ bg: string;
31
+ }[];
32
+ }
33
+ export declare const featureCards: FeatureCardsProps;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.featureCards = exports.dashboardMockupMenuList = void 0;
4
+ var lucide_react_1 = require("lucide-react");
5
+ exports.dashboardMockupMenuList = {
6
+ domain: "subdomain.edupilotpro.com",
7
+ items: [
8
+ { icon: lucide_react_1.LayoutDashboard, label: "Dashboard", active: true },
9
+ { icon: lucide_react_1.Users, label: "Students", active: false },
10
+ { icon: lucide_react_1.BookOpen, label: "Courses", active: false },
11
+ { icon: lucide_react_1.Calendar, label: "Attendance", active: false },
12
+ { icon: lucide_react_1.Receipt, label: "Finance", active: false },
13
+ { icon: lucide_react_1.BarChart3, label: "Analytics", active: false },
14
+ ],
15
+ schoolName: "Greenwood Academy",
16
+ stats: [
17
+ { label: "Students", value: "412", color: "text-blue-600" },
18
+ { label: "Teachers", value: "34", color: "text-purple-600" },
19
+ { label: "Attendance", value: "97%", color: "text-green-700" },
20
+ { label: "Fees Due", value: "$4.2k", color: "text-rose-600" },
21
+ ],
22
+ enrollmentHeading: "Recent Enrollments",
23
+ enrollmentList: [
24
+ { name: "Aisha Patel", class: "Grade 7A", status: "Active" },
25
+ { name: "Omar Faruk", class: "Grade 5B", status: "Active" },
26
+ { name: "Lena Müller", class: "Grade 9C", status: "Pending" },
27
+ ],
28
+ };
29
+ exports.featureCards = {
30
+ items: [
31
+ {
32
+ icon: lucide_react_1.Users,
33
+ title: "AI Admission Assistant",
34
+ description: "Automate your entire admissions pipeline. The AI Admission Assistant captures enquiries, collects documents, and moves students from application to enrolled — with no manual follow-up from your team.",
35
+ highlights: [
36
+ "Digital admission forms & document collection",
37
+ "AI-guided enrolment workflows",
38
+ "Family portal with sibling linking",
39
+ "Searchable student & family database",
40
+ ],
41
+ accent: "text-blue-700",
42
+ bg: "bg-blue-50 dark:bg-blue-950/30",
43
+ href: "/features/student-family",
44
+ },
45
+ {
46
+ icon: lucide_react_1.BookOpen,
47
+ title: "Academic Structure Engine",
48
+ description: "Configure your school's full academic hierarchy — year groups, sections, subjects, courses — once. Every module (attendance, grades, timetables) inherits this structure automatically.",
49
+ highlights: [
50
+ "Multi-level class & section hierarchy",
51
+ "Subject catalogue & course management",
52
+ "Teacher assignments & academic year config",
53
+ "Supports all curriculum frameworks",
54
+ ],
55
+ accent: "text-purple-700",
56
+ bg: "bg-purple-50 dark:bg-purple-950/30",
57
+ href: "/features/academic-structure",
58
+ },
59
+ {
60
+ icon: lucide_react_1.Calendar,
61
+ title: "AI Attendance Assistant",
62
+ description: "Teachers mark an entire class present in one tap. The AI Attendance Assistant records every entry, detects chronic absence patterns, and automatically notifies parents — all without lifting a finger.",
63
+ highlights: [
64
+ "One-tap bulk attendance marking",
65
+ "AI-powered absence pattern detection",
66
+ "Instant automated parent notifications",
67
+ "Conflict-free visual timetable builder",
68
+ ],
69
+ accent: "text-green-700",
70
+ bg: "bg-green-50 dark:bg-green-950/30",
71
+ href: "/features/attendance-schedules",
72
+ },
73
+ {
74
+ icon: lucide_react_1.BarChart3,
75
+ title: "AI Reporting Assistant",
76
+ description: "From test scores to polished report cards in one click. The AI Reporting Assistant calculates GPA, ranks students, flags underperformers, and generates print-ready PDFs at the end of every term.",
77
+ highlights: [
78
+ "Automatic GPA & class rank calculation",
79
+ "AI-flagged underperformance alerts",
80
+ "One-click PDF report card generation",
81
+ "Multi-term performance trend analytics",
82
+ ],
83
+ accent: "text-orange-700",
84
+ bg: "bg-orange-50 dark:bg-orange-950/30",
85
+ href: "/features/grades-assessments",
86
+ },
87
+ {
88
+ icon: lucide_react_1.CreditCard,
89
+ title: "AI Fee Reminder Automation",
90
+ description: "Build your fee structures once. EduPilotPro auto-generates invoices every term, sends escalating payment reminders automatically, and keeps your outstanding fee dashboard updated in real time.",
91
+ highlights: [
92
+ "Automated termly invoice generation",
93
+ "AI fee reminder sequences to parents",
94
+ "Real-time outstanding balance tracking",
95
+ "Discount, scholarship & exemption management",
96
+ ],
97
+ accent: "text-rose-700",
98
+ bg: "bg-rose-50 dark:bg-rose-950/30",
99
+ href: "/features/financial-management",
100
+ },
101
+ {
102
+ icon: lucide_react_1.Shield,
103
+ title: "Smart Role-Based Access",
104
+ description: "Every staff member, teacher, and parent sees exactly what they need. Five permission levels ensure data is protected, workflows are clean, and nothing leaks between roles.",
105
+ highlights: [
106
+ "5-tier role hierarchy (Admin → Parent)",
107
+ "Staff & workspace user management",
108
+ "Audit-ready access and activity logs",
109
+ "Secure, GDPR-compliant authentication",
110
+ ],
111
+ accent: "text-teal-700",
112
+ bg: "bg-teal-50 dark:bg-teal-950/30",
113
+ href: "/features/access-control",
114
+ },
115
+ ],
116
+ };
@@ -0,0 +1,13 @@
1
+ import React, { FC } from "react";
2
+ export interface FeatureCardProps {
3
+ accent: string;
4
+ bg: string;
5
+ description: string;
6
+ highlights: string[];
7
+ href: string;
8
+ Icon: React.ComponentType<{
9
+ className?: string;
10
+ }>;
11
+ title: string;
12
+ }
13
+ export declare const FeatureCard: FC<FeatureCardProps>;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FeatureCard = void 0;
7
+ var react_1 = __importDefault(require("react"));
8
+ var link_1 = __importDefault(require("next/link"));
9
+ var lucide_react_1 = require("lucide-react");
10
+ var card_1 = require("../../components/ui/card");
11
+ var FeatureCard = function (_a) {
12
+ var accent = _a.accent, bg = _a.bg, description = _a.description, highlights = _a.highlights, href = _a.href, Icon = _a.Icon, title = _a.title;
13
+ return (react_1.default.createElement(link_1.default, { key: title, href: href, prefetch: false, className: "group flex" },
14
+ react_1.default.createElement(card_1.Card, { className: "flex w-full flex-col border-border/60 transition-all hover:shadow-lg hover:border-primary/40" },
15
+ react_1.default.createElement(card_1.CardHeader, { className: "pb-3" },
16
+ react_1.default.createElement("div", { className: "mb-4 inline-flex h-11 w-11 items-center justify-center rounded-xl ".concat(bg) },
17
+ react_1.default.createElement(Icon, { className: "h-5 w-5 ".concat(accent) })),
18
+ react_1.default.createElement(card_1.CardTitle, { className: "text-lg" }, title)),
19
+ react_1.default.createElement(card_1.CardContent, { className: "flex flex-col flex-1 gap-4" },
20
+ react_1.default.createElement("p", { className: "text-sm text-muted-foreground leading-relaxed" }, description),
21
+ react_1.default.createElement("ul", { className: "mt-auto space-y-1.5" }, highlights.map(function (h) { return (react_1.default.createElement("li", { key: h, className: "flex items-start gap-2 text-sm text-foreground" },
22
+ react_1.default.createElement(lucide_react_1.CheckCircle2, { className: "mt-0.5 h-4 w-4 shrink-0 ".concat(accent) }),
23
+ h)); })),
24
+ react_1.default.createElement("div", { className: "mt-2 flex items-center gap-1 text-xs font-semibold ".concat(accent) },
25
+ "Learn more",
26
+ react_1.default.createElement(lucide_react_1.ArrowRight, { className: "h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" }))))));
27
+ };
28
+ exports.FeatureCard = FeatureCard;
@@ -0,0 +1,3 @@
1
+ import { FC } from "react";
2
+ import { DashboardMockupProps } from "../../data/edupilotpro-v1";
3
+ export declare const DashboardMockup: FC<DashboardMockupProps>;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DashboardMockup = void 0;
7
+ var react_1 = __importDefault(require("react"));
8
+ var lucide_react_1 = require("lucide-react");
9
+ var DashboardMockup = function (_a) {
10
+ var domain = _a.domain, enrollmentHeading = _a.enrollmentHeading, enrollmentList = _a.enrollmentList, items = _a.items, schoolName = _a.schoolName, stats = _a.stats;
11
+ return (react_1.default.createElement("div", { className: "relative mx-auto w-full max-w-3xl" },
12
+ react_1.default.createElement("div", { className: "absolute inset-0 -z-10 scale-95 blur-3xl opacity-30 bg-gradient-to-br from-primary via-purple-500 to-blue-500 rounded-2xl" }),
13
+ react_1.default.createElement("div", { className: "rounded-xl border border-border/60 bg-background shadow-2xl overflow-hidden" },
14
+ react_1.default.createElement("div", { className: "flex items-center gap-2 border-b border-border/60 bg-muted/50 px-4 py-3" },
15
+ react_1.default.createElement("span", { className: "h-3 w-3 rounded-full bg-red-400" }),
16
+ react_1.default.createElement("span", { className: "h-3 w-3 rounded-full bg-yellow-400" }),
17
+ react_1.default.createElement("span", { className: "h-3 w-3 rounded-full bg-green-400" }),
18
+ react_1.default.createElement("div", { className: "ml-3 flex-1 rounded-md bg-background/70 border border-border/40 px-3 py-1 text-xs text-muted-foreground" }, domain)),
19
+ react_1.default.createElement("div", { className: "flex h-[340px] sm:h-[380px]" },
20
+ react_1.default.createElement("div", { className: "hidden w-44 flex-shrink-0 border-r border-border/40 bg-muted/30 p-3 sm:block" },
21
+ react_1.default.createElement("div", { className: "mb-4 flex items-center gap-2" },
22
+ react_1.default.createElement("span", { className: "flex h-6 w-6 items-center justify-center rounded bg-primary text-primary-foreground" },
23
+ react_1.default.createElement(lucide_react_1.GraduationCap, { className: "h-4 w-4" })),
24
+ react_1.default.createElement("span", { className: "text-xs font-bold" }, "EduPilotPro")),
25
+ items.map(function (_a) {
26
+ var Icon = _a.icon, label = _a.label, active = _a.active;
27
+ return (react_1.default.createElement("div", { key: label, className: "mb-1 flex items-center gap-2 rounded-md px-2 py-1.5 text-xs ".concat(active
28
+ ? "bg-primary/10 text-primary font-semibold"
29
+ : "text-muted-foreground") },
30
+ react_1.default.createElement(Icon, { className: "h-3.5 w-3.5 shrink-0" }),
31
+ label));
32
+ })),
33
+ react_1.default.createElement("div", { className: "flex-1 overflow-hidden p-4" },
34
+ react_1.default.createElement("p", { className: "mb-3 text-sm font-semibold text-foreground" },
35
+ "Overview \u2014 ",
36
+ schoolName),
37
+ react_1.default.createElement("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-4" }, stats.map(function (_a) {
38
+ var label = _a.label, value = _a.value, color = _a.color;
39
+ return (react_1.default.createElement("div", { key: label, className: "rounded-lg border border-border/50 bg-card p-3" },
40
+ react_1.default.createElement("p", { className: "text-[10px] text-muted-foreground" }, label),
41
+ react_1.default.createElement("p", { className: "text-lg font-bold ".concat(color) }, value)));
42
+ })),
43
+ react_1.default.createElement("div", { className: "mt-3 rounded-lg border border-border/50 bg-card p-3" },
44
+ react_1.default.createElement("p", { className: "mb-2 text-xs font-semibold text-foreground" }, enrollmentHeading),
45
+ react_1.default.createElement("div", { className: "space-y-1.5" }, enrollmentList.map(function (_a) {
46
+ var name = _a.name, cls = _a.class, status = _a.status;
47
+ return (react_1.default.createElement("div", { key: name, className: "flex items-center justify-between text-[10px]" },
48
+ react_1.default.createElement("span", { className: "font-medium text-foreground" }, name),
49
+ react_1.default.createElement("span", { className: "text-muted-foreground" }, cls),
50
+ react_1.default.createElement("span", { className: "rounded-full px-1.5 py-0.5 text-[9px] font-medium ".concat(status === "Active"
51
+ ? "bg-green-100 text-green-700"
52
+ : "bg-yellow-100 text-yellow-700") }, status)));
53
+ }))))))));
54
+ };
55
+ exports.DashboardMockup = DashboardMockup;