diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..8c3cbf6 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,840 @@ +# Copilot Instructions - @ciscode/ui-widget-kit + +> **Purpose**: Reusable React component library providing dashboard widgets, grid layout, data tables, dynamic forms, and the main app template shell โ€” consumed as a shared UI layer across all `@ciscode/*` frontend apps. + +--- + +## ๐ŸŽฏ Package Overview + +**Package**: `@ciscode/ui-widget-kit` +**Version**: 1.0.x +**Type**: React Frontend Component Library +**Purpose**: Centralized, typed, accessible UI components and hooks โ€” the single source of truth for dashboard layout, data display, and form patterns across all host apps + +### This Package Provides: + +- Component-Hook-Model (CHM) architecture โ€” the frontend equivalent of CSR +- `DashboardGrid` + `WidgetContainer` โ€” drag, resize, actions, layout persistence +- `TableDataCustom` โ€” typed columns, selection, sorting, filtering, pagination, inline edit +- `ControlledZodDynamicForm` โ€” schema-driven, fully controlled form with Zod validation +- `Template` โ€” full app shell (sidebar, navbar, footer, layout config) +- `Breadcrumb`, `Loader`, `PageTitle`, `Sidebar`, `DarkModeSwitcher` โ€” common UI +- `DefaultChartAdapter` โ€” pluggable SVG chart adapter +- Auth hooks: `useLogin`, `useRegister`, `usePasswordReset` +- Utility hooks: `useLocalStorage`, `useColorMode`, `useKeyboardNavigation` +- Accessibility hooks: `useLiveRegion`, `useFocusTrap` +- Complete TypeScript types for all public contracts +- Tailwind-compatible styling (utility classes only โ€” no Tailwind dependency on host) +- Vitest unit/integration tests with 80% coverage threshold +- Playwright E2E tests +- Changesets for version management +- Husky + lint-staged for code quality + +--- + +## ๐Ÿ—๏ธ Module Architecture + +**WidgetKit uses Component-Hook-Model (CHM) pattern โ€” the frontend equivalent of CSR.** + +> **WHY CHM for UI libraries?** Reusable component libraries need clean separation between rendering (Components), logic/state (Hooks), and data contracts (Models). This mirrors CSR but for the React world: Components are Controllers, Hooks are Services, Models are Entities/DTOs. + +``` +src/ + โ”œโ”€โ”€ index.ts # PUBLIC API โ€” all exports go through here + โ”œโ”€โ”€ assets/ + โ”‚ โ””โ”€โ”€ styles/ + โ”‚ โ””โ”€โ”€ style.css # Global styles โ€” imported once by host app + โ”‚ + โ”œโ”€โ”€ components/ # UI Layer (rendering + interaction) + โ”‚ โ”œโ”€โ”€ Dashboard/ + โ”‚ โ”‚ โ””โ”€โ”€ Widgets/ + โ”‚ โ”‚ โ”œโ”€โ”€ DashboardGrid.tsx # Grid orchestrator + โ”‚ โ”‚ โ”œโ”€โ”€ WidgetContainer.tsx # Single widget shell (drag, resize, actions) + โ”‚ โ”‚ โ””โ”€โ”€ ChartAdapters/ # Pluggable chart adapters + โ”‚ โ”œโ”€โ”€ Table/ + โ”‚ โ”‚ โ”œโ”€โ”€ TableDataCustom.tsx # Public wrapper (exported) + โ”‚ โ”‚ โ””โ”€โ”€ TableDataCustomBase.tsx # Internal base (not exported directly) + โ”‚ โ”œโ”€โ”€ Form/ + โ”‚ โ”‚ โ””โ”€โ”€ ZodDynamicForm.tsx # ControlledZodDynamicForm + โ”‚ โ”œโ”€โ”€ Breadcrumbs/ + โ”‚ โ”‚ โ””โ”€โ”€ Breadcrumb.tsx + โ”‚ โ””โ”€โ”€ (Loader, PageTitle, Sidebar, etc.) + โ”‚ + โ”œโ”€โ”€ hooks/ # Logic Layer (state, side effects, auth) + โ”‚ โ”œโ”€โ”€ useLocalStorage.ts + โ”‚ โ”œโ”€โ”€ useColorMode.ts + โ”‚ โ”œโ”€โ”€ useGeneratePageNumbers.ts + โ”‚ โ”œโ”€โ”€ useLogin.ts + โ”‚ โ”œโ”€โ”€ useRegister.ts + โ”‚ โ”œโ”€โ”€ usePasswordReset.ts + โ”‚ โ”œโ”€โ”€ useA11y.ts # useLiveRegion, useFocusTrap + โ”‚ โ””โ”€โ”€ useKeyboardNavigation.ts + โ”‚ + โ”œโ”€โ”€ models/ # Type Layer (contracts, configs) + โ”‚ โ”œโ”€โ”€ ColumnConfigTable.ts + โ”‚ โ”œโ”€โ”€ FieldConfigDynamicForm.ts + โ”‚ โ”œโ”€โ”€ ToolbarItemModel.ts + โ”‚ โ”œโ”€โ”€ SidebarItemModel.ts + โ”‚ โ””โ”€โ”€ DashboardWidget.ts + โ”‚ + โ””โ”€โ”€ main/ # App Shell + โ”œโ”€โ”€ dashboard.tsx # Template component (full layout) + โ””โ”€โ”€ layoutTypes.ts # Layout config types +``` + +**Responsibility Layers:** + +| Layer | Responsibility | Examples | +| -------------- | ------------------------------------------------------ | ---------------------------------------------------- | +| **Components** | Rendering, user interaction, composition | `DashboardGrid`, `TableDataCustom`, `Breadcrumb` | +| **Hooks** | State logic, side effects, auth, a11y | `useColorMode`, `useLogin`, `useFocusTrap` | +| **Models** | TypeScript contracts, config shapes (no runtime logic) | `ColumnConfigTable`, `DashboardLayout`, `WidgetType` | +| **Main/Shell** | App-level layout orchestration | `Template`, `layoutTypes` | +| **Assets** | Global stylesheet โ€” CSS variables, base resets | `style.css` | + +### Layer Rules โ€” STRICTLY ENFORCED + +| Layer | Can import from | Cannot import from | +| ------------ | ------------------------------- | ----------------------------- | +| `components` | `hooks`, `models`, `assets` | `main` internals | +| `hooks` | `models` | `components` | +| `models` | Nothing internal | `hooks`, `components`, `main` | +| `main` | `components`, `hooks`, `models` | โ€” | + +> **The golden rule**: `models/` is pure TypeScript โ€” no React imports, no hooks, no runtime code. If you're writing `import { useState }` inside a model file, it's in the wrong layer. + +--- + +## ๐Ÿ“ Naming Conventions + +### Files + +**Pattern**: `PascalCase` for components, `camelCase` for hooks and models + +| Type | Example | Directory | +| ----------------- | ------------------------- | ------------------------------------------------- | +| Component | `DashboardGrid.tsx` | `src/components//` | +| Hook | `useColorMode.ts` | `src/hooks/` | +| Model / Type file | `ColumnConfigTable.ts` | `src/models/` | +| Layout type | `layoutTypes.ts` | `src/main/` | +| Chart adapter | `DefaultChartAdapter.tsx` | `src/components/Dashboard/Widgets/ChartAdapters/` | +| Test (unit) | `DashboardGrid.test.tsx` | `tests/unit/` | +| Test (E2E) | `dashboard.spec.ts` | `tests/e2e/` | + +### Code Naming + +- **Components**: `PascalCase` โ†’ `DashboardGrid`, `WidgetContainer`, `TableDataCustom` +- **Hooks**: `camelCase` with `use` prefix โ†’ `useLocalStorage`, `useFocusTrap` +- **Types / Interfaces**: `PascalCase` โ†’ `ColumnConfigTable`, `DashboardLayout`, `WidgetType` +- **Props types**: named `Props` locally, exported with component name โ†’ `DashboardProps`, `TableDataCustomProps` +- **Enum values**: `UPPER_SNAKE_CASE` (if used) or string literals +- **Constants**: `UPPER_SNAKE_CASE` โ†’ `DEFAULT_GRID_COLS`, `PERSIST_KEY_PREFIX` + +### Component Pattern โ€” ALWAYS follow this structure + +```tsx +import type { JSX } from 'react'; +import type { ColumnConfigTable } from '../../models/ColumnConfigTable'; + +// 1. Define Props type explicitly โ€” never inline in function signature +type Props = { + columns: ColumnConfigTable[]; + data: Row[]; + onRowClick?: (row: Row) => void; + className?: string; +}; + +// 2. Default export for components +export default function TableDataCustom({ + columns, + data, + onRowClick, + className, +}: Props): JSX.Element { + // ... +} + +// 3. Export Props type for consumers +export type { Props as TableDataCustomProps }; +``` + +**Rules:** + +- โœ… Always explicit `Props` type โ€” never `React.FC<{...}>` inline +- โœ… Always explicit `JSX.Element` return type +- โœ… Default export for components, named exports for hooks and types +- โœ… Destructure props in the function signature +- โŒ Never use `React.FC` โ€” it hides return type and adds implicit children +- โŒ Never use `any` for prop types โ€” use `unknown` or proper generics + +--- + +## ๐Ÿ“ฆ Public API โ€” `src/index.ts` + +**Everything the host app can use must be exported here. Anything not in `src/index.ts` is private.** + +```typescript +// โœ… All host app imports must come from the package root +import { + DashboardGrid, + TableDataCustom, + ControlledZodDynamicForm, + useColorMode, + useLocalStorage, + type ColumnConfigTable, + type DashboardLayout, +} from '@ciscode/ui-widget-kit'; + +// Also import the stylesheet once in your app entry point +import '@ciscode/ui-widget-kit/style.css'; +``` + +**Export rules:** + +```typescript +// โœ… Exported โ€” public API +export { default as DashboardGrid } from './components/Dashboard/Widgets/DashboardGrid'; +export { default as TableDataCustom } from './components/Table/TableDataCustom'; +export { default as useColorMode } from './hooks/useColorMode'; +export type { ColumnConfigTable } from './models/ColumnConfigTable'; + +// โŒ NEVER export +// Internal base components (TableDataCustomBase โ€” use the wrapper) +// Internal sub-components (WidgetContainer when used only inside DashboardGrid) +// Internal utilities and helpers not part of the public contract +``` + +**When adding a new export**, always ask: _is this something a host app needs directly, or is it an internal implementation detail?_ If unsure โ€” keep it private. + +--- + +## โš™๏ธ Peer Dependencies + +Host apps must install peer dependencies. Only install what you use: + +| Peer dep | Required for | Optional | +| -------------------------------- | ------------------------------------------ | -------- | +| `react` + `react-dom` >= 18 | Everything | No | +| `react-router-dom` ^7 | `Breadcrumb`, router-integrated components | No | +| `zod` ^4 | `ControlledZodDynamicForm` | No | +| `react-select` ^5 | Select/dropdown form fields | No | +| `@ciscode/ui-translate-core` | i18n/translations in components | No | +| `@ciscode/ui-authentication-kit` | Auth hooks (`useLogin`, etc.) | Yes | + +> **Rule for new peer deps**: mark optional in `peerDependenciesMeta`, guard usage with a clear runtime error if missing, document in README. Never add a required peer dep without a MAJOR version bump. + +--- + +## ๐Ÿงฉ Core Components + +### `DashboardGrid` + +```tsx + +``` + +**Rules for widget types**: `card`, `stat`, `progress`, `activity`, `chart`, `custom`. Adding a new widget type requires updating `WidgetType` in `models/DashboardWidget.ts` and the renderer in `DashboardGrid.tsx`. + +### `TableDataCustom` + +```tsx +const columns: ColumnConfigTable[] = [ + { key: 'name', title: 'Name', sortable: true }, + { key: 'email', title: 'Email' }, +]; + + + columns={columns} + data={users} + pagination={{ currentPage, totalPages, totalItems, onPageChange }} +/>; +``` + +### `ControlledZodDynamicForm` + +Fully controlled โ€” host app owns state. No internal state for field values. + +```tsx + setFormState((prev) => ({ ...prev, [key]: val }))} + onSubmit={(values) => handleSubmit(values)} +/> +``` + +### `Template` (App Shell) + +```tsx +