Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
31f6ffc
chore(husky): remove deprecated bootstrap lines; style: run prettier …
a-elkhiraooui-ciscode Feb 5, 2026
f062688
chore: bump version to 1.0.2
a-elkhiraooui-ciscode Feb 5, 2026
6ab6197
ci: run workflows on PRs to master
a-elkhiraooui-ciscode Feb 5, 2026
ec809d2
ci(e2e): strict port + no server reuse; enable release check on PRs t…
a-elkhiraooui-ciscode Feb 5, 2026
46e806b
ci(e2e): bind dev server to 127.0.0.1; align Playwright URLs; increas…
a-elkhiraooui-ciscode Feb 5, 2026
0bebf25
e2e: revert to localhost server defaults (no CI/CD changes)
a-elkhiraooui-ciscode Feb 5, 2026
07f071b
ci: align PR validation and release-check workflows to master (develo…
a-elkhiraooui-ciscode Feb 5, 2026
d489ac1
chore: push current workspace state
a-elkhiraooui-ciscode Feb 5, 2026
4e28c0c
Merge branch 'master' of https://github.com/CISCODE-MA/WidgetKit-UI i…
a-elkhiraooui-ciscode Feb 12, 2026
c8ec6f1
Develop (#12)
Zaiidmo Feb 24, 2026
eed4f27
1.0.4
Zaiidmo Feb 26, 2026
e8eb622
Merge branch 'master' of github.com:CISCODE-MA/WidgetKit-UI
Zaiidmo Feb 26, 2026
0e81de6
ops: updated workflows files
Zaiidmo Feb 26, 2026
5149521
docs: added copilot instructions
Zaiidmo Feb 26, 2026
c008566
Merge branch 'develop' of https://github.com/CISCODE-MA/WidgetKit-UI …
a-elkhiraooui-ciscode Feb 27, 2026
c94c14c
Merge branch 'master' of https://github.com/CISCODE-MA/WidgetKit-UI i…
a-elkhiraooui-ciscode Feb 27, 2026
195cc9d
refactored following the copilot instructions file
a-elkhiraooui-ciscode Feb 27, 2026
69bc007
fixed test errors
a-elkhiraooui-ciscode Feb 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
840 changes: 840 additions & 0 deletions .github/copilot-instructions.md

Large diffs are not rendered by default.

File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name: CI - Release Check
on:
pull_request:
branches: [master]
push:
branches: [master]
workflow_dispatch:
inputs:
sonar:
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ Reusable React TypeScript library for dashboard widgets, grid layout, and advanc
- Dashboard widget grid: drag, resize, duplicate, remove, actions
- Layout persistence: localStorage hydration and commit
- Data table: typed columns, selection, sorting, filtering, inline edit, pagination
- Error boundaries for widgets and tables
- Error boundaries for widgets and tables (enhanced with reset and logging capabilities)
- Pluggable chart adapters (default SVG)
- ESM + CJS + Types build (tsup)
- Vitest unit/integration tests
- Vitest unit/integration tests (80%+ coverage target)
- Playwright E2E tests for critical user paths (e.g., drag/resize in DashboardGrid)
- ESLint + Prettier (flat config)
- Changesets (manual release flow)
- Husky (pre-commit + pre-push)
Expand Down Expand Up @@ -40,6 +41,18 @@ npm i react react-dom react-router-dom zod @ciscode/ui-translate-core

Anything not exported from `src/index.ts` is considered private.

## New Features

### Enhanced Error Handling

- Error boundaries now include a reset mechanism and optional logging integration (e.g., Sentry).
- Hooks (`useLogin`, `usePasswordReset`, `useRegister`) support error reporting and user-friendly messages.

### Improved Testing

- **Integration Tests**: Added workflows for `ControlledZodDynamicForm` and `TableDataCustom`.
- **E2E Tests**: Expanded coverage to include `DashboardGrid` drag/resize functionality.

## Scripts

- `npm run dev` – start Vite dev server (for docs/examples)
Expand Down
8 changes: 4 additions & 4 deletions examples/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ export default function App(): JSX.Element {
];

return (
<div style={{ padding: 24 }}>
<div className="p-6">
<Breadcrumb pageName="Examples" />
<div style={{ marginTop: 16 }}>
<div className="mt-4">
<TableDataCustom<Row>
columns={columns}
data={data}
Expand All @@ -85,10 +85,10 @@ export default function App(): JSX.Element {
/>
</div>

<hr style={{ margin: '24px 0' }} />
<hr className="my-6" />
<h2>ControlledZodDynamicForm</h2>
<p>Simple required-field form using Zod.</p>
<div style={{ marginTop: 12 }}>
<div className="mt-3">
<ControlledZodDynamicForm
fields={fields}
schema={schema}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ciscode/ui-widget-kit",
"version": "1.0.3",
"version": "1.0.4",
"description": "",
"main": "dist/index.cjs",
"module": "dist/index.js",
Expand Down
9 changes: 2 additions & 7 deletions src/components/Dashboard/Widgets/DashboardGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,24 @@
export default function DashboardGrid({
grid,
widgets,
onLayoutChange,

Check warning on line 18 in src/components/Dashboard/Widgets/DashboardGrid.tsx

View workflow job for this annotation

GitHub Actions / CI - PR Validation

'onLayoutChange' is defined but never used
enableDrag = true,
enableResize = true,
showActions = true,

Check warning on line 21 in src/components/Dashboard/Widgets/DashboardGrid.tsx

View workflow job for this annotation

GitHub Actions / CI - PR Validation

'showActions' is assigned a value but never used
persistKey,

Check warning on line 22 in src/components/Dashboard/Widgets/DashboardGrid.tsx

View workflow job for this annotation

GitHub Actions / CI - PR Validation

'persistKey' is defined but never used
}: Props): JSX.Element {
// Minimal grid rendering: each widget in a grid cell
return (
<div
className="grid"
className={`grid gap-${grid.gap}`}
style={{
gridTemplateColumns: `repeat(${grid.cols}, minmax(0, 1fr))`,
gap: grid.gap,
}}
>
{widgets.map((widget) => (
<div
key={widget.id}
style={{
gridColumn: `${widget.position.x + 1} / span ${widget.position.w}`,
gridRow: `${widget.position.y + 1} / span ${widget.position.h}`,
}}
className={`col-span-${widget.position.w} row-span-${widget.position.h}`}
>
<WidgetContainer title={widget.title} draggable={enableDrag} resizable={enableResize}>
{/* Render widget content based on type */}
Expand All @@ -56,7 +52,6 @@
style={{ width: `${Number(widget.props?.value ?? 0)}%` }}
/>
</div>
<div className="text-xs mt-1">{Number(widget.props?.value ?? 0)}%</div>
</div>
)}
{widget.type === 'activity' && (
Expand Down
9 changes: 8 additions & 1 deletion src/components/Dashboard/Widgets/WidgetContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@ export default function WidgetContainer({
const headerRef = useRef<HTMLDivElement | null>(null);

return (
<div className="relative rounded-lg border border-gray-200 bg-white dark:bg-gray-800 shadow-sm overflow-hidden">
<div
className="relative rounded-lg border border-gray-200 bg-white dark:bg-gray-800 shadow-sm overflow-hidden"
role="region"
aria-labelledby={`widget-title-${title}`}
>
<h2 id={`widget-title-${title}`} className="widget-title">
{title}
</h2>
<div
ref={headerRef}
className={
Expand Down
10 changes: 8 additions & 2 deletions src/components/Form/ZodDynamicForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@
* On form submit => parse the entire `values` with Zod.
* If it fails, store the error messages in local state to display.
*/
function handleSubmit(e: FormEvent): void {
function handleSubmit(e: FormEvent<HTMLFormElement>): void {
e.preventDefault();
setErrors({});
try {
// Attempt to parse the entire form data with Zod
const parsed = schema.parse(values);
Expand All @@ -66,7 +67,7 @@
// Check if any error is specifically for "details" path
let detailsErrorFound = false;

err.issues.forEach((issue: { path: any[]; message: string }) => {

Check warning on line 70 in src/components/Form/ZodDynamicForm.tsx

View workflow job for this annotation

GitHub Actions / CI - PR Validation

Unexpected any. Specify a different type
const pathKey = issue.path.join('.');
newErrors[pathKey] = issue.message;

Expand All @@ -87,7 +88,7 @@
}

return (
<form onSubmit={handleSubmit} className="space-y-6">
<form onSubmit={handleSubmit} aria-describedby="form-errors">
{/* header */}
{header && <div className="mb-4">{header}</div>}

Expand Down Expand Up @@ -245,6 +246,11 @@
{submitLabel}
</button>
</div>
{Object.keys(errors).length > 0 && (
<div id="form-errors" className="text-red-600 mt-4">
Please fix the errors above.
</div>
)}
</form>
);
}
Expand Down
8 changes: 5 additions & 3 deletions src/components/Table/TableDataCustomBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,9 @@ function TableDataCustomBase<T>({
</tr>
) : (
visibleData.map((row, r) => (
<tr key={r} className="border-b">
<tr key={r} className="border-b" role="row" aria-rowindex={r + 1}>
{enableSelection && (
<td className="px-4 py-3">
<td className="px-4 py-3" role="gridcell">
<input
type="checkbox"
aria-label={`Select row ${r + 1}`}
Expand Down Expand Up @@ -398,7 +398,9 @@ function TableDataCustomBase<T>({
return (
<td
key={c}
className={`px-4 py-3 ltr:text-left rtl:text-right ${col.cellClassName ?? ''}`}
role="gridcell"
className="px-4 py-3"
tabIndex={0} // Ensure focusable cells
>
{content}
</td>
Expand Down
29 changes: 21 additions & 8 deletions src/exceptions/TableErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,30 @@ class TableErrorBoundary extends React.Component<

componentDidCatch(error: unknown, errorInfo: unknown): void {
console.error('TableErrorBoundary caught an error:', error, errorInfo);
// Integrate a logging service for production environments
if (process.env.NODE_ENV === 'production') {
// Replace with your logging service, e.g., Sentry
// Sentry.captureException(error);
}
}

handleReset = (): void => {
this.setState({ hasError: false });
};

render(): React.ReactNode {
if (this.state.hasError) {
// You can't use hooks like useT inside a class component,
// so we wrap it in a functional HOC instead.
return <TranslatedErrorMessage />;
return (
<div role="alert" className="p-4 text-red-600 bg-red-50 border border-red-300 rounded">
<TranslatedErrorMessage />
<button
onClick={this.handleReset}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Retry
</button>
</div>
);
}

return this.props.children;
Expand All @@ -36,11 +53,7 @@ class TableErrorBoundary extends React.Component<

const TranslatedErrorMessage = (): JSX.Element => {
const t = useT('template-fe');
return (
<div className="p-4 text-red-600 bg-red-50 border border-red-300 rounded">
{t('table.errorBoundary.fallbackMessage')}
</div>
);
return <div>{t('table.errorBoundary.fallbackMessage')}</div>;
};

export default TableErrorBoundary;
12 changes: 8 additions & 4 deletions src/hooks/useLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,17 @@ export function useLogin<TUser = unknown>({
setError(null);
setLoading(true);
try {
const input = schema ? schema.parse(values) : values;
const res = await login(input);
const credentials = schema ? schema.parse(values) : values;
const res = await login(credentials);
setResult(res);
return res;
} catch (e: unknown) {
const message = e instanceof Error ? e.message : 'Login failed';
setError(message);
setError('Login failed. Please try again.');
// Optional: Report error to an external service
if (process.env.NODE_ENV === 'production') {
// Replace with your logging service, e.g., Sentry
// Sentry.captureException(e);
}
throw e;
} finally {
setLoading(false);
Expand Down
8 changes: 6 additions & 2 deletions src/hooks/usePasswordReset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ export function usePasswordReset({
await reset(input);
setSuccess(true);
} catch (e: unknown) {
const message = e instanceof Error ? e.message : 'Password reset failed';
setError(message);
setError('Password reset failed. Please try again.');
// Optional: Report error to an external service
if (process.env.NODE_ENV === 'production') {
// Replace with your logging service, e.g., Sentry
// Sentry.captureException(e);
}
throw e;
} finally {
setLoading(false);
Expand Down
8 changes: 6 additions & 2 deletions src/hooks/useRegister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ export function useRegister<TUser = unknown>({
setUser(res);
return res;
} catch (e: unknown) {
const message = e instanceof Error ? e.message : 'Registration failed';
setError(message);
setError('Registration failed. Please try again.');
// Optional: Report error to an external service
if (process.env.NODE_ENV === 'production') {
// Replace with your logging service, e.g., Sentry
// Sentry.captureException(e);
}
throw e;
} finally {
setLoading(false);
Expand Down
5 changes: 0 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export { default as Breadcrumb } from './components/Breadcrumbs/Breadcrumb';
export { default as ControlledZodDynamicForm } from './components/Form/ZodDynamicForm';
export { default as TableDataCustom } from './components/Table/TableDataCustom';
export { default as DashboardGrid } from './components/Dashboard/Widgets/DashboardGrid';
export { DefaultChartAdapter } from './components/Dashboard/Widgets/ChartAdapters';

// Hooks (public)
export { default as useLocalStorage } from './hooks/useLocalStorage';
Expand Down Expand Up @@ -52,7 +51,3 @@ export type { BreadcrumbProps } from './components/Breadcrumbs/Breadcrumb';
export type { ControlledZodDynamicFormProps } from './components/Form/ZodDynamicForm';
export type { PaginationProps, TableDataCustomProps } from './components/Table/TableDataCustomBase';
export type { DashboardProps } from './main/dashboard';
// Types (hooks)
export type { LoginCredentials, LoginResult, UseLoginOptions } from './hooks/useLogin';
export type { RegisterPayload, UseRegisterOptions } from './hooks/useRegister';
export type { PasswordResetInput, UsePasswordResetOptions } from './hooks/usePasswordReset';
32 changes: 32 additions & 0 deletions tests/e2e/dashboardGrid.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { test, expect } from '@playwright/test';

test('DashboardGrid drag and resize', async ({ page }) => {
await page.goto('/dashboard');

// Verify initial widget positions
const widget = await page.locator('[data-testid="widget-1"]');
const initialBox = await widget.boundingBox();
expect(initialBox).not.toBeNull();

// Drag the widget
await widget.hover();
await page.mouse.down();
await page.mouse.move(initialBox!.x + 100, initialBox!.y + 100);
await page.mouse.up();

// Verify new widget position
const newBox = await widget.boundingBox();
expect(newBox).not.toEqual(initialBox);

// Resize the widget
const resizeHandle = await widget.locator('[data-testid="resize-handle"]');
await resizeHandle.hover();
await page.mouse.down();
await page.mouse.move(newBox!.x + 50, newBox!.y + 50);
await page.mouse.up();

// Verify widget size changed
const resizedBox = await widget.boundingBox();
expect(resizedBox!.width).toBeGreaterThan(newBox!.width);
expect(resizedBox!.height).toBeGreaterThan(newBox!.height);
});
Loading