feat: Add generic components for UI

This commit is contained in:
deytenit committed 2026-01-31 17:52:45 +03:00
1 parent 08a7f44588
commit 054cf4f5e3
38 files changed
+1562 -241

No files matched your search

+6
View File
@@ -31,12 +31,18 @@
"@ermnvldmr/eslint-config": "workspace:*",
"@ermnvldmr/jest-config": "workspace:*",
"@ermnvldmr/vite-config": "workspace:*",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"jest": "^30.2.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"ts-jest": "^29.4.6",
"typescript": "^5.8.3",
"vite": "^7.2.2"
},
"peerDependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0",
"tailwindcss": "^4.1.4"
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
* Shared helper utilities for common functionality.
*/
export { cn } from './cn/cn';
export { cn } from './cn/cn';
+1
View File
@@ -1,2 +1,3 @@
export * from './types';
export * from './functions';
export * from './react';
@@ -0,0 +1,11 @@
import { castRef } from './castRef';
import type React from 'react';
describe('react/castRef', () => {
it('should cast a ref to a different type', () => {
const ref = { current: null } as unknown as React.ForwardedRef<HTMLDivElement>;
const casted = castRef<HTMLSpanElement>(ref);
expect(casted).toBe(ref);
});
});
+19
View File
@@ -0,0 +1,19 @@
import type React from 'react';
/**
* Safely casts a React ref to a specific type.
* This is a controlled escape hatch for situations where TypeScript's
* built-in Ref types are too restrictive (e.g., in generic forwardRef components).
*
* @param ref - The ref to cast
* @returns The casted ref
* @example
* ```tsx
* <Component ref={castRef<HTMLDivElement>(ref)} />
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function castRef<T>(ref: React.ForwardedRef<any>): React.ForwardedRef<T> {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return ref as React.ForwardedRef<T>;
}
@@ -0,0 +1,14 @@
import React from 'react';
import { genericMemo } from './genericMemo';
describe('react/genericMemo', () => {
it('should memoize a component', () => {
const Component = ({ name }: { name: string }) => <div>{name}</div>;
const Memoized = genericMemo(Component);
expect(Memoized).toBeDefined();
// In React, memo returns a special object with type: component
expect((Memoized as any).type).toBe(Component);
});
});
@@ -0,0 +1,16 @@
import React from 'react';
/**
* A type-safe version of React.memo that preserves generic types.
*
* @param component - The component to memoize
* @returns The memoized component
* @example
* ```tsx
* export const MyComponent = genericMemo(MyGenericComponent);
* ```
*/
export function genericMemo<T>(component: T): T {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
return React.memo(component as any) as any as T;
}
+10
View File
@@ -0,0 +1,10 @@
export { castRef } from './castRef/castRef';
export { genericMemo } from './genericMemo/genericMemo';
import type React from 'react';
/**
* Common props for any React element.
* Useful for forwarding props to dynamic components.
*/
export type ReactElementProps = React.HTMLAttributes<HTMLElement> & React.AllHTMLAttributes<HTMLElement>;