@pattern-stack/frontend-patterns 0.0.4 → 0.0.6
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/atoms/composed/SalesPanel/SalesPanel.d.ts +19 -0
- package/dist/atoms/composed/SalesPanel/SalesPanel.d.ts.map +1 -0
- package/dist/atoms/composed/SalesPanel/index.d.ts +2 -0
- package/dist/atoms/composed/SalesPanel/index.d.ts.map +1 -0
- package/dist/atoms/composed/SalesPanel/mockSalesData.d.ts +63 -0
- package/dist/atoms/composed/SalesPanel/mockSalesData.d.ts.map +1 -0
- package/dist/atoms/composed/index.d.ts +1 -0
- package/dist/atoms/composed/index.d.ts.map +1 -1
- package/dist/atoms/types/entity-config.d.ts +117 -0
- package/dist/atoms/types/entity-config.d.ts.map +1 -0
- package/dist/atoms/types/index.d.ts +2 -0
- package/dist/atoms/types/index.d.ts.map +1 -1
- package/dist/atoms/types/navigation.d.ts +30 -0
- package/dist/atoms/types/navigation.d.ts.map +1 -0
- package/dist/atoms/ui/ErrorBoundary.d.ts +1 -1
- package/dist/atoms/ui/button.d.ts +1 -1
- package/dist/atoms/utils/icon-resolver.d.ts +72 -0
- package/dist/atoms/utils/icon-resolver.d.ts.map +1 -0
- package/dist/atoms/utils/metric-engine.d.ts +30 -0
- package/dist/atoms/utils/metric-engine.d.ts.map +1 -0
- package/dist/atoms/utils/utils.d.ts +2 -0
- package/dist/atoms/utils/utils.d.ts.map +1 -1
- package/dist/features/auth/components/ProtectedRoute.d.ts +1 -1
- package/dist/frontend-patterns.css +1 -1
- package/dist/index.es.js +402 -14
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +402 -14
- package/dist/index.js.map +1 -1
- package/dist/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.d.ts +16 -0
- package/dist/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.d.ts.map +1 -0
- package/dist/molecules/layout/DashboardWithSidePanel/index.d.ts +2 -0
- package/dist/molecules/layout/DashboardWithSidePanel/index.d.ts.map +1 -0
- package/dist/molecules/layout/NavigationContext.d.ts +15 -0
- package/dist/molecules/layout/NavigationContext.d.ts.map +1 -0
- package/dist/molecules/layout/Sidebar.d.ts.map +1 -1
- package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts +2 -0
- package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts.map +1 -1
- package/dist/molecules/layout/index.d.ts +3 -0
- package/dist/molecules/layout/index.d.ts.map +1 -1
- package/dist/templates/factory.d.ts +2 -1
- package/dist/templates/factory.d.ts.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/package.json +7 -3
- package/src/App.tsx +11 -1
- package/src/__tests__/atoms/composed/databadge.test.tsx +106 -0
- package/src/__tests__/atoms/composed/statcard.test.tsx +133 -0
- package/src/__tests__/atoms/utils/icon-resolver.test.tsx +140 -0
- package/src/atoms/composed/SalesPanel/SalesPanel.tsx +116 -0
- package/src/atoms/composed/SalesPanel/index.ts +1 -0
- package/src/atoms/composed/SalesPanel/mockSalesData.ts +151 -0
- package/src/atoms/composed/index.ts +1 -0
- package/src/atoms/types/entity-config.ts +127 -0
- package/src/atoms/types/index.ts +3 -1
- package/src/atoms/types/navigation.ts +43 -0
- package/src/atoms/utils/icon-resolver.tsx +54 -0
- package/src/atoms/utils/metric-engine.ts +236 -0
- package/src/atoms/utils/utils.ts +4 -2
- package/src/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.tsx +42 -0
- package/src/molecules/layout/DashboardWithSidePanel/index.ts +1 -0
- package/src/molecules/layout/NavigationContext.tsx +63 -0
- package/src/molecules/layout/Sidebar.tsx +10 -23
- package/src/molecules/layout/SidebarButton/SidebarButton.tsx +32 -10
- package/src/molecules/layout/index.ts +4 -1
- package/src/organisms/entity/CategoryBreakdownPanel.tsx +427 -0
- package/src/organisms/entity/EntityListPanel.tsx +339 -0
- package/src/organisms/entity/MetricsOverviewPanel.tsx +236 -0
- package/src/organisms/entity/TrendAnalysisPanel.tsx +337 -0
- package/src/organisms/entity/index.ts +4 -0
- package/src/organisms/index.ts +5 -1
- package/src/pages/AdminShowcase/AdminDashboardShowcase.tsx +77 -75
- package/src/pages/AdminShowcase/SalesPerformanceDashboard.tsx +158 -0
- package/src/pages/AdminShowcase/index.tsx +2 -1
- package/src/pages/EntityShowcase/EntityManagementShowcase.tsx +137 -0
- package/src/pages/EntityShowcase/EntityPerformanceShowcase.tsx +117 -0
- package/src/pages/EntityShowcase/index.ts +2 -0
- package/src/pages/EntityTemplateExample.tsx +229 -0
- package/src/pages/TestEntityTemplate.tsx +40 -0
- package/src/pages/index.ts +2 -1
- package/src/templates/entity/EntityManagementTemplate.tsx +430 -0
- package/src/templates/entity/EntityPerformanceDashboardTemplate.tsx +277 -0
- package/src/templates/entity/configs/financial-config.ts +141 -0
- package/src/templates/entity/configs/index.ts +1 -0
- package/src/templates/entity/index.ts +3 -0
- package/src/templates/factory.tsx +14 -7
- package/src/templates/financial/FinancialDashboardTemplate.tsx +326 -0
- package/src/templates/index.ts +4 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface DashboardWithSidePanelProps {
|
|
3
|
+
/** Main dashboard content */
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
/** Side panel component to display */
|
|
6
|
+
sidePanel?: React.ReactNode;
|
|
7
|
+
/** Whether to show the side panel */
|
|
8
|
+
showSidePanel?: boolean;
|
|
9
|
+
/** Side panel width in Tailwind units (e.g., 72, 80) */
|
|
10
|
+
sidePanelWidth?: number;
|
|
11
|
+
/** Additional CSS classes */
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const DashboardWithSidePanel: React.FC<DashboardWithSidePanelProps>;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=DashboardWithSidePanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DashboardWithSidePanel.d.ts","sourceRoot":"","sources":["../../../../src/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,UAAU,2BAA2B;IACnC,6BAA6B;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,sCAAsC;IACtC,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,qCAAqC;IACrC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CAyBxE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/molecules/layout/DashboardWithSidePanel/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { NavigationConfig, NavigationItem } from '../../atoms/types';
|
|
3
|
+
interface NavigationContextType {
|
|
4
|
+
navigation: NavigationConfig;
|
|
5
|
+
setNavigation: (config: NavigationConfig) => void;
|
|
6
|
+
}
|
|
7
|
+
interface NavigationProviderProps {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
initialNavigation?: NavigationConfig;
|
|
10
|
+
}
|
|
11
|
+
export declare const NavigationProvider: ({ children, initialNavigation }: NavigationProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare const useNavigation: () => NavigationContextType;
|
|
13
|
+
export declare const getNavigationItems: (config: NavigationConfig) => NavigationItem[];
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=NavigationContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationContext.d.ts","sourceRoot":"","sources":["../../../src/molecules/layout/NavigationContext.tsx"],"names":[],"mappings":"AAAA,OAAc,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEzE,UAAU,qBAAqB;IAC7B,UAAU,EAAE,gBAAgB,CAAA;IAC5B,aAAa,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAA;CAClD;AAID,UAAU,uBAAuB;IAC/B,QAAQ,EAAE,SAAS,CAAA;IACnB,iBAAiB,CAAC,EAAE,gBAAgB,CAAA;CACrC;AAaD,eAAO,MAAM,kBAAkB,GAAI,iCAAiC,uBAAuB,4CAc1F,CAAA;AAED,eAAO,MAAM,aAAa,6BAMzB,CAAA;AAGD,eAAO,MAAM,kBAAkB,GAAI,QAAQ,gBAAgB,KAAG,cAAc,EAW3E,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar.d.ts","sourceRoot":"","sources":["../../../src/molecules/layout/Sidebar.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Sidebar.d.ts","sourceRoot":"","sources":["../../../src/molecules/layout/Sidebar.tsx"],"names":[],"mappings":"AAQA,UAAU,YAAY;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,OAAO,GAAI,eAAe,YAAY,4CAsHlD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SidebarButton.d.ts","sourceRoot":"","sources":["../../../../src/molecules/layout/SidebarButton/SidebarButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,UAAU,kBAAkB;IAC1B,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,
|
|
1
|
+
{"version":3,"file":"SidebarButton.d.ts","sourceRoot":"","sources":["../../../../src/molecules/layout/SidebarButton/SidebarButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,UAAU,kBAAkB;IAC1B,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAwGtD,CAAC"}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export { PageTemplate, type PageTemplateProps } from './PageTemplate';
|
|
2
2
|
export { ShowcaseSection } from './ShowcaseSection';
|
|
3
3
|
export { AppLayout } from './AppLayout';
|
|
4
|
+
export { DashboardWithSidePanel } from './DashboardWithSidePanel';
|
|
4
5
|
export { SectionHeader } from './SectionHeader';
|
|
5
6
|
export { SidebarButton } from './SidebarButton';
|
|
6
7
|
export { AppHeader } from './AppHeader';
|
|
7
8
|
export { SidebarProvider, useSidebar } from './SidebarContext';
|
|
9
|
+
export { NavigationProvider, useNavigation, getNavigationItems } from './NavigationContext';
|
|
10
|
+
export { Sidebar } from './Sidebar';
|
|
8
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/molecules/layout/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/molecules/layout/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
-
import type { AuthConfig } from '../atoms/types';
|
|
3
|
+
import type { AuthConfig, NavigationConfig } from '../atoms/types';
|
|
4
4
|
export interface AppConfig {
|
|
5
5
|
title: string;
|
|
6
6
|
description?: string;
|
|
@@ -14,6 +14,7 @@ export interface AppConfig {
|
|
|
14
14
|
theme?: string;
|
|
15
15
|
darkMode?: boolean;
|
|
16
16
|
auth?: AuthConfig;
|
|
17
|
+
navigation?: NavigationConfig;
|
|
17
18
|
customProviders?: React.ComponentType<{
|
|
18
19
|
children: ReactNode;
|
|
19
20
|
}>[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/templates/factory.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/templates/factory.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQtC,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAGlE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B,eAAe,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE,CAAC,EAAE,CAAA;CACjE;AAGD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,SAAS,CAAA;IACvB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,KAAK,IAAI,CAAA;IACvD,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC;AAGD,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,WAAW,CAoItE;AAGD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAE1D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC7B,MAAM,gBAAgB,CAAC;AAGxB,cAAc,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC7B,MAAM,gBAAgB,CAAC;AAGxB,cAAc,SAAS,CAAC;AAOxB,OAAO,EACL,cAAc,EACd,eAAe,EACf,KAAK,SAAS,EACd,KAAK,WAAW,EACjB,MAAM,WAAW,CAAC"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@pattern-stack/frontend-patterns",
|
|
3
3
|
"description": "Production-ready React frontend template with atomic architecture patterns. Build ultra-lean applications by importing shared UI foundation patterns.",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.6",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"react",
|
|
8
8
|
"typescript",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"scripts": {
|
|
45
45
|
"dev": "vite",
|
|
46
46
|
"build": "tsc -b && vite build",
|
|
47
|
-
"build:lib": "npm run clean && vite build --mode library &&
|
|
47
|
+
"build:lib": "npm run clean && vite build --mode library && npm run build:types",
|
|
48
|
+
"build:types": "tsc --project tsconfig.lib.json",
|
|
48
49
|
"clean": "rm -rf dist",
|
|
49
50
|
"lint": "eslint .",
|
|
50
51
|
"lint:fix": "eslint . --fix",
|
|
@@ -102,9 +103,12 @@
|
|
|
102
103
|
"@vitejs/plugin-react": "^4.4.1",
|
|
103
104
|
"@vitest/coverage-v8": "^3.2.1",
|
|
104
105
|
"autoprefixer": "^10.4.21",
|
|
106
|
+
"concurrently": "^9.1.2",
|
|
107
|
+
"cors": "^2.8.5",
|
|
105
108
|
"eslint": "^9.25.0",
|
|
106
109
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
107
110
|
"eslint-plugin-react-refresh": "^0.4.19",
|
|
111
|
+
"express": "^5.1.0",
|
|
108
112
|
"globals": "^16.0.0",
|
|
109
113
|
"jsdom": "^26.1.0",
|
|
110
114
|
"postcss": "^8.5.3",
|
|
@@ -115,4 +119,4 @@
|
|
|
115
119
|
"vite": "^6.3.5",
|
|
116
120
|
"vitest": "^3.2.1"
|
|
117
121
|
}
|
|
118
|
-
}
|
|
122
|
+
}
|
package/src/App.tsx
CHANGED
|
@@ -15,8 +15,12 @@ import {
|
|
|
15
15
|
ComponentShowcase,
|
|
16
16
|
AdminDashboardShowcase,
|
|
17
17
|
AdminCRUDShowcase,
|
|
18
|
-
AdminDetailShowcase
|
|
18
|
+
AdminDetailShowcase,
|
|
19
|
+
SalesPerformanceDashboard,
|
|
20
|
+
EntityPerformanceShowcase,
|
|
21
|
+
EntityManagementShowcase
|
|
19
22
|
} from './pages';
|
|
23
|
+
import { EntityTemplateExample } from './pages/EntityTemplateExample';
|
|
20
24
|
|
|
21
25
|
function App() {
|
|
22
26
|
return (
|
|
@@ -41,6 +45,12 @@ function App() {
|
|
|
41
45
|
<Route path="admin/dashboard" element={<ErrorBoundary><AdminDashboardShowcase /></ErrorBoundary>} />
|
|
42
46
|
<Route path="admin/users" element={<ErrorBoundary><AdminCRUDShowcase /></ErrorBoundary>} />
|
|
43
47
|
<Route path="admin/user/:id" element={<ErrorBoundary><AdminDetailShowcase /></ErrorBoundary>} />
|
|
48
|
+
<Route path="admin/sales" element={<ErrorBoundary><SalesPerformanceDashboard /></ErrorBoundary>} />
|
|
49
|
+
|
|
50
|
+
{/* Entity Template Showcases */}
|
|
51
|
+
<Route path="entity/performance" element={<ErrorBoundary><EntityPerformanceShowcase /></ErrorBoundary>} />
|
|
52
|
+
<Route path="entity/management" element={<ErrorBoundary><EntityManagementShowcase /></ErrorBoundary>} />
|
|
53
|
+
<Route path="entity/template-example" element={<ErrorBoundary><EntityTemplateExample /></ErrorBoundary>} />
|
|
44
54
|
|
|
45
55
|
{/* Fallback */}
|
|
46
56
|
<Route path="*" element={<ErrorBoundary><ComponentShowcasePage /></ErrorBoundary>} />
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '../../utils'
|
|
3
|
+
import { DataBadge } from '../../../atoms/composed/DataBadge'
|
|
4
|
+
|
|
5
|
+
describe('DataBadge (Composed)', () => {
|
|
6
|
+
describe('status variant', () => {
|
|
7
|
+
it('renders success status badge', () => {
|
|
8
|
+
render(<DataBadge variant="status" status="success">Active</DataBadge>)
|
|
9
|
+
|
|
10
|
+
const badge = screen.getByText('Active')
|
|
11
|
+
expect(badge).toBeInTheDocument()
|
|
12
|
+
expect(badge.className).toContain('status-success')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('renders warning status badge', () => {
|
|
16
|
+
render(<DataBadge variant="status" status="warning">Pending</DataBadge>)
|
|
17
|
+
|
|
18
|
+
const badge = screen.getByText('Pending')
|
|
19
|
+
expect(badge.className).toContain('status-warning')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('renders error status badge', () => {
|
|
23
|
+
render(<DataBadge variant="status" status="error">Failed</DataBadge>)
|
|
24
|
+
|
|
25
|
+
const badge = screen.getByText('Failed')
|
|
26
|
+
expect(badge.className).toContain('status-error')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('renders info status badge', () => {
|
|
30
|
+
render(<DataBadge variant="status" status="info">Processing</DataBadge>)
|
|
31
|
+
|
|
32
|
+
const badge = screen.getByText('Processing')
|
|
33
|
+
expect(badge.className).toContain('status-info')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('renders neutral status badge', () => {
|
|
37
|
+
render(<DataBadge variant="status" status="neutral">Draft</DataBadge>)
|
|
38
|
+
|
|
39
|
+
const badge = screen.getByText('Draft')
|
|
40
|
+
expect(badge.className).toContain('status-neutral')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('category variant', () => {
|
|
45
|
+
it('renders category 1 badge', () => {
|
|
46
|
+
render(<DataBadge variant="category" category={1}>Category 1</DataBadge>)
|
|
47
|
+
|
|
48
|
+
const badge = screen.getByText('Category 1')
|
|
49
|
+
expect(badge).toBeInTheDocument()
|
|
50
|
+
expect(badge.className).toContain('badge-category-1')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('renders category 5 badge', () => {
|
|
54
|
+
render(<DataBadge variant="category" category={5}>Category 5</DataBadge>)
|
|
55
|
+
|
|
56
|
+
const badge = screen.getByText('Category 5')
|
|
57
|
+
expect(badge.className).toContain('badge-category-5')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('renders category 8 badge', () => {
|
|
61
|
+
render(<DataBadge variant="category" category={8}>Category 8</DataBadge>)
|
|
62
|
+
|
|
63
|
+
const badge = screen.getByText('Category 8')
|
|
64
|
+
expect(badge.className).toContain('badge-category-8')
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('applies additional className props', () => {
|
|
69
|
+
render(
|
|
70
|
+
<DataBadge variant="status" status="success" className="custom-class">
|
|
71
|
+
Custom
|
|
72
|
+
</DataBadge>
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const badge = screen.getByText('Custom')
|
|
76
|
+
expect(badge.className).toContain('custom-class')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('renders with component data attribute', () => {
|
|
80
|
+
render(
|
|
81
|
+
<DataBadge variant="status" status="info">
|
|
82
|
+
Test
|
|
83
|
+
</DataBadge>
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
expect(screen.getByText('Test')).toHaveAttribute('data-component-name', 'DataBadge')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('automatically uses default category', () => {
|
|
90
|
+
render(<DataBadge>Default</DataBadge>)
|
|
91
|
+
|
|
92
|
+
const badge = screen.getByText('Default')
|
|
93
|
+
expect(badge.className).toContain('badge-category-1')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('handles string and number status values', () => {
|
|
97
|
+
const { rerender } = render(
|
|
98
|
+
<DataBadge variant="status" status="success">String Status</DataBadge>
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
expect(screen.getByText('String Status')).toBeInTheDocument()
|
|
102
|
+
|
|
103
|
+
rerender(<DataBadge variant="category" category={3}>Number Category</DataBadge>)
|
|
104
|
+
expect(screen.getByText('Number Category')).toBeInTheDocument()
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { render, screen, userEvent } from '../../utils'
|
|
3
|
+
import { StatCard } from '../../../atoms/composed/StatCard'
|
|
4
|
+
|
|
5
|
+
describe('StatCard (Composed)', () => {
|
|
6
|
+
it('renders basic stat card with title and value', () => {
|
|
7
|
+
render(<StatCard title="Total Users" value="1,234" />)
|
|
8
|
+
|
|
9
|
+
expect(screen.getByText('Total Users')).toBeInTheDocument()
|
|
10
|
+
expect(screen.getByText('1,234')).toBeInTheDocument()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('renders with subtitle', () => {
|
|
14
|
+
render(
|
|
15
|
+
<StatCard
|
|
16
|
+
title="Revenue"
|
|
17
|
+
value="$45,678"
|
|
18
|
+
subtitle="+12% from last month"
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
expect(screen.getByText('Revenue')).toBeInTheDocument()
|
|
23
|
+
expect(screen.getByText('$45,678')).toBeInTheDocument()
|
|
24
|
+
expect(screen.getByText('+12% from last month')).toBeInTheDocument()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('renders with icon', () => {
|
|
28
|
+
render(
|
|
29
|
+
<StatCard
|
|
30
|
+
title="Orders"
|
|
31
|
+
value="567"
|
|
32
|
+
icon="ShoppingCart"
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
expect(screen.getByText('Orders')).toBeInTheDocument()
|
|
37
|
+
expect(screen.getByText('567')).toBeInTheDocument()
|
|
38
|
+
// Icon should be rendered (we can't easily test the actual icon, but we can test it doesn't crash)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('applies category theming', () => {
|
|
42
|
+
render(<StatCard title="Test" value="123" category={3} />)
|
|
43
|
+
|
|
44
|
+
const card = screen.getByText('Test').closest('[data-component-name="StatCard"]')
|
|
45
|
+
expect(card).toBeInTheDocument()
|
|
46
|
+
expect(card).toHaveAttribute('data-component-name', 'StatCard')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('handles click events', async () => {
|
|
50
|
+
const user = userEvent.setup()
|
|
51
|
+
const handleClick = vi.fn()
|
|
52
|
+
|
|
53
|
+
render(
|
|
54
|
+
<StatCard
|
|
55
|
+
title="Clickable Card"
|
|
56
|
+
value="999"
|
|
57
|
+
onClick={handleClick}
|
|
58
|
+
/>
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const card = screen.getByText('Clickable Card').closest('[role="button"]')
|
|
62
|
+
expect(card).toBeInTheDocument()
|
|
63
|
+
|
|
64
|
+
await user.click(card!)
|
|
65
|
+
expect(handleClick).toHaveBeenCalledOnce()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('applies hover effects when clickable', () => {
|
|
69
|
+
render(
|
|
70
|
+
<StatCard
|
|
71
|
+
title="Hoverable"
|
|
72
|
+
value="555"
|
|
73
|
+
onClick={() => {}}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const card = screen.getByText('Hoverable').closest('[role="button"]')
|
|
78
|
+
expect(card).toHaveClass('cursor-pointer')
|
|
79
|
+
expect(card).toHaveClass('hover:shadow-md')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('does not apply click styles when not clickable', () => {
|
|
83
|
+
render(<StatCard title="Static Card" value="111" />)
|
|
84
|
+
|
|
85
|
+
const card = screen.getByText('Static Card').closest('div')
|
|
86
|
+
expect(card).not.toHaveAttribute('role', 'button')
|
|
87
|
+
expect(card).not.toHaveClass('cursor-pointer')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('displays numbers as provided', () => {
|
|
91
|
+
render(<StatCard title="Big Number" value={1234567} />)
|
|
92
|
+
|
|
93
|
+
expect(screen.getByText('1234567')).toBeInTheDocument()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('handles string and number values', () => {
|
|
97
|
+
const { rerender } = render(<StatCard title="String" value="Custom Text" />)
|
|
98
|
+
expect(screen.getByText('Custom Text')).toBeInTheDocument()
|
|
99
|
+
|
|
100
|
+
rerender(<StatCard title="Number" value={42} />)
|
|
101
|
+
expect(screen.getByText('42')).toBeInTheDocument()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('applies custom className', () => {
|
|
105
|
+
render(
|
|
106
|
+
<StatCard
|
|
107
|
+
title="Custom"
|
|
108
|
+
value="123"
|
|
109
|
+
className="custom-stat-card"
|
|
110
|
+
/>
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
const card = screen.getByText('Custom').closest('.custom-stat-card')
|
|
114
|
+
expect(card).toBeInTheDocument()
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('renders loading state', () => {
|
|
118
|
+
render(<StatCard title="Loading" value="..." isLoading={true} />)
|
|
119
|
+
|
|
120
|
+
const card = screen.getByText('Loading').closest('[data-component-name="StatCard"]')
|
|
121
|
+
expect(card).toBeInTheDocument()
|
|
122
|
+
// Check for skeleton elements in loading state
|
|
123
|
+
const skeletons = card?.querySelectorAll('.animate-pulse')
|
|
124
|
+
expect(skeletons?.length).toBeGreaterThan(0)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('renders with data component name', () => {
|
|
128
|
+
render(<StatCard title="Test Card" value="123" />)
|
|
129
|
+
|
|
130
|
+
const card = screen.getByText('Test Card').closest('[data-component-name="StatCard"]')
|
|
131
|
+
expect(card).toHaveAttribute('data-component-name', 'StatCard')
|
|
132
|
+
})
|
|
133
|
+
})
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { render, screen } from '../../utils'
|
|
3
|
+
import { Icon, getIcon, iconMap } from '../../../atoms/utils/icon-resolver'
|
|
4
|
+
|
|
5
|
+
// Mock console.warn to avoid noise in tests
|
|
6
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
7
|
+
|
|
8
|
+
describe('Icon Resolver', () => {
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
consoleSpy.mockClear()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
describe('Icon component', () => {
|
|
14
|
+
it('renders valid icon', () => {
|
|
15
|
+
render(<Icon name="Home" data-testid="home-icon" />)
|
|
16
|
+
|
|
17
|
+
const icon = screen.getByTestId('home-icon')
|
|
18
|
+
expect(icon).toBeInTheDocument()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('applies default className', () => {
|
|
22
|
+
render(<Icon name="Users" data-testid="users-icon" />)
|
|
23
|
+
|
|
24
|
+
const icon = screen.getByTestId('users-icon')
|
|
25
|
+
expect(icon).toHaveClass('w-5', 'h-5')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('applies custom className', () => {
|
|
29
|
+
render(<Icon name="Settings" className="w-8 h-8 text-blue-500" data-testid="settings-icon" />)
|
|
30
|
+
|
|
31
|
+
const icon = screen.getByTestId('settings-icon')
|
|
32
|
+
expect(icon).toHaveClass('w-8', 'h-8', 'text-blue-500')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('applies size prop', () => {
|
|
36
|
+
render(<Icon name="Search" size={24} data-testid="search-icon" />)
|
|
37
|
+
|
|
38
|
+
const icon = screen.getByTestId('search-icon')
|
|
39
|
+
expect(icon).toHaveAttribute('width', '24')
|
|
40
|
+
expect(icon).toHaveAttribute('height', '24')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('renders fallback icon for invalid name', () => {
|
|
44
|
+
render(<Icon name="InvalidIcon" data-testid="fallback-icon" />)
|
|
45
|
+
|
|
46
|
+
const icon = screen.getByTestId('fallback-icon')
|
|
47
|
+
expect(icon).toBeInTheDocument()
|
|
48
|
+
expect(consoleSpy).toHaveBeenCalledWith('Icon "InvalidIcon" not found. Using default Menu icon.')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('handles undefined icon name gracefully', () => {
|
|
52
|
+
render(<Icon name={undefined as any} data-testid="undefined-icon" />)
|
|
53
|
+
|
|
54
|
+
const icon = screen.getByTestId('undefined-icon')
|
|
55
|
+
expect(icon).toBeInTheDocument()
|
|
56
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('getIcon function', () => {
|
|
61
|
+
it('returns icon component for valid name', () => {
|
|
62
|
+
const HomeIcon = getIcon('Home')
|
|
63
|
+
expect(HomeIcon).toBeDefined()
|
|
64
|
+
expect(typeof HomeIcon).toBe('function')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('returns Menu icon for invalid name', () => {
|
|
68
|
+
const InvalidIcon = getIcon('InvalidIcon' as any)
|
|
69
|
+
expect(InvalidIcon).toBeDefined()
|
|
70
|
+
expect(typeof InvalidIcon).toBe('function')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('returns Menu icon for undefined name', () => {
|
|
74
|
+
const UndefinedIcon = getIcon(undefined as any)
|
|
75
|
+
expect(UndefinedIcon).toBeDefined()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('iconMap', () => {
|
|
80
|
+
it('contains expected common icons', () => {
|
|
81
|
+
const expectedIcons = [
|
|
82
|
+
'Home', 'Users', 'Settings', 'Search', 'Menu',
|
|
83
|
+
'ChevronDown', 'ChevronRight', 'X', 'Plus',
|
|
84
|
+
'ShoppingCart', 'Calendar', 'Mail', 'Phone'
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
expectedIcons.forEach(iconName => {
|
|
88
|
+
expect(iconMap).toHaveProperty(iconName)
|
|
89
|
+
expect(typeof iconMap[iconName]).toBe('function')
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('has reasonable number of icons', () => {
|
|
94
|
+
const iconCount = Object.keys(iconMap).length
|
|
95
|
+
expect(iconCount).toBeGreaterThan(30)
|
|
96
|
+
expect(iconCount).toBeLessThan(100) // Keep it manageable
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('all icon map values are functions', () => {
|
|
100
|
+
Object.values(iconMap).forEach(iconComponent => {
|
|
101
|
+
expect(typeof iconComponent).toBe('function')
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('Icon rendering variations', () => {
|
|
107
|
+
const testIcons = ['Home', 'Users', 'Settings', 'Search', 'Bell']
|
|
108
|
+
|
|
109
|
+
testIcons.forEach(iconName => {
|
|
110
|
+
it(`renders ${iconName} icon without errors`, () => {
|
|
111
|
+
render(<Icon name={iconName} data-testid={`${iconName.toLowerCase()}-icon`} />)
|
|
112
|
+
|
|
113
|
+
const icon = screen.getByTestId(`${iconName.toLowerCase()}-icon`)
|
|
114
|
+
expect(icon).toBeInTheDocument()
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('Integration with real icon names', () => {
|
|
120
|
+
it('renders navigation icons correctly', () => {
|
|
121
|
+
const navIcons = ['Home', 'Users', 'Settings', 'FileText', 'BarChart']
|
|
122
|
+
|
|
123
|
+
navIcons.forEach(iconName => {
|
|
124
|
+
const { unmount } = render(<Icon name={iconName} />)
|
|
125
|
+
// If it renders without throwing, it's valid
|
|
126
|
+
unmount()
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('renders action icons correctly', () => {
|
|
131
|
+
const actionIcons = ['Plus', 'Edit', 'Trash2', 'Save', 'Download']
|
|
132
|
+
|
|
133
|
+
actionIcons.forEach(iconName => {
|
|
134
|
+
const { unmount } = render(<Icon name={iconName} />)
|
|
135
|
+
// If it renders without throwing, it's valid
|
|
136
|
+
unmount()
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
})
|