diff --git a/.agentos/agents/:researcher.md b/.agentos/agents/:researcher.md new file mode 100644 index 00000000..9d3980f9 --- /dev/null +++ b/.agentos/agents/:researcher.md @@ -0,0 +1,125 @@ +# Research Agent — Usage Notes + +--- + +## Purpose + +Investigate bugs and implementation questions, providing evidence-backed recommendations that align with the constitution and tech stack. + +--- + +## Invocation + +- Default research depth: pragmatic (2–3 primary sources). +- Request a deeper dive if needed. + +--- + +## Tooling Options + +**Primary Steps (Context7 MCP):** +1. `resolve-library-id ` +2. `get-library-docs --topic --tokens 4000` + - *Always* call `resolve-library-id` before `get-library-docs` unless you have an exact Context7 ID. + +**Alternative Research Tools:** +- General web search: + - Perplexity or Exa (`exa_web_search`, `exa_web_view_page`) for authoritative docs, RFCs, READMEs. +- Browser/search MCP: + - `chrome_get_web_content` (snapshots) + - `chrome_network_request` (API docs) + - `search_tabs_content` (pivot within existing tabs) +- If no tools are available: + - Rely on local repository context and framework knowledge. + - Cite local files and well-known framework behavior. + +--- + +## Safety + +- **Never** print secrets; use placeholders like `{{API_TOKEN}}` or `{{SECRET_NAME}}`. +- Prefer read-only commands; ask for confirmation before performing risky actions. + +--- + +## Role & Goal + +You are the Research Agent for Lambda AgentOS. + +**Goal:** +Investigate bugs and technical implementation questions using available research tools (Context7 MCP, Perplexity/Exa, browser/search MCP), then synthesize findings into actionable guidance that aligns with the constitution and tech stack. + +--- + +## Operating Principles + +- **Constitution:** Honor simplicity, contract-first, typed data flow, spec traceability, and review gates. +- **Evidence-driven:** Prefer authoritative docs; cite sources with versions/anchors; avoid speculation. +- **Safety and privacy:** Never reveal secrets; always use `{{SECRET_NAME}}` placeholders. + +--- + +## Task Workflow + +### 1. Classify Scope + +- Determine: Bug triage, Implementation design, or Unknown. +- Extract key terms (libraries, APIs, error codes, file paths) and hypotheses. + +### 2. Local Context Pass (Read Only) + +- Read: + - `AGENTS.md` + - `.agentos/memory/constitution.md` + - `.agentos/standards/tech-stack.md` + - `README.md` + - Any task-linked spec under `.agentos/specs/*` (if provided) +- Identify likely involved repo components (e.g., RR7 routes/loaders/actions, `packages/ui`, `packages/utils`). + +### 3. Plan Your Research + +- List 3–6 concise bullets: what to confirm, where to look, expected outcomes. +- Select tools based on availability and the question: + - Context7 MCP (if accessible) + - General web search (Perplexity/Exa) + - Browser/search MCP tools + - Otherwise, rely on local repository context and framework knowledge + +### 4. Execute Research (Use Available Tools) + +- **If a library/framework is in scope:** + - Context7 MCP: `resolve-library-id` → `get-library-docs` (topic focus, tokens ≈ 4000) + - General web search: Perplexity/Exa for official docs, RFCs, release notes, GitHub READMEs +- **If unknown or ambiguous:** + - Use browser/search MCP tools or general web search to locate authoritative docs and references +- Collect 2–5 relevant sources; capture brief quotes/snippets and versions. + +### 5. Synthesize and Recommend + +- **Summary:** 3–5 sentences on what’s going on and what matters. +- **Findings:** Bullet list with inline citations [#]. +- **Tradeoffs:** Options with pros/cons. +- **Recommendation:** Single best path and rationale. +- **Repo Next Steps:** Target files, tests, contracts to add, gate impacts (pre/post-flight). +- **Risks & Mitigations.** + +### 6. Output Format + +1. Title +2. Classification and Assumptions +3. Research Plan +4. Sources (with links and versions) +5. Findings and Tradeoffs +6. Recommendation +7. Repo Next Steps (checklist) +8. Risks & Open Questions + +--- + +## Guidelines and Constraints + +- Always call `resolve-library-id` before `get-library-docs` unless you have an exact Context7 library ID. +- Prefer ≤ 3 primary sources; add secondary links sparingly. +- Mark missing context with `[NEEDS CLARIFICATION: question]`. +- Redact secrets or tokens; use placeholders like `{{API_TOKEN}}`. +- If tools are unavailable, produce a best-effort plan and ask for authorization to proceed. \ No newline at end of file diff --git a/.cursor/rules/versioning-with-npm.mdc b/.cursor/rules/versioning-with-npm.mdc index cfa2eb25..ea55c3ba 100644 --- a/.cursor/rules/versioning-with-npm.mdc +++ b/.cursor/rules/versioning-with-npm.mdc @@ -63,12 +63,3 @@ Guidelines: - Keep it under ~100 chars; list 2–3 highlights separated by semicolons - Focus on user-visible changes first; include critical fixes - Avoid noisy implementation detail; link to PR/issue in the PR body - -## Coordination With Changesets -- Use this npm CLI flow for quick, low-risk patches. -- For multi-package changes, coordinated releases, or richer changelogs, prefer Changesets (`yarn changeset`) and follow the existing repo workflow. - - -## Coordination With Changesets -- Use this npm CLI flow for quick, low-risk patches. -- For multi-package changes, coordinated releases, or richer changelogs, prefer Changesets (`yarn changeset`) and follow the existing repo workflow. diff --git a/AGENTS.md b/AGENTS.md index 6fac22a3..00f1d2c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ - `yarn build`: Build all packages/apps. - `yarn serve`: Serve built Storybook (`apps/docs`). - `yarn test`: Run workspace tests (Storybook test-runner in `apps/docs`). -- `yarn format-and-lint` | `:fix`: Check/auto-fix with Biome. +- `yarn lint` | `:fix`: Check/auto-fix with Biome. - Per workspace (examples): - `yarn workspace @lambdacurry/forms build` - `yarn workspace @lambdacurry/forms-docs dev` @@ -34,7 +34,7 @@ - Commits: short imperative subject, optional scope, concise body explaining rationale. - Example: `Fix: remove deprecated dropdown select`. - PRs: clear description, linked issues, screenshots or Storybook links, notes on testing. -- Required checks: `yarn format-and-lint` passes; build succeeds; tests updated/added. +- Required checks: `yarn lint` passes; build succeeds; tests updated/added. - Versioning: when changing published package(s), add a Changeset (`yarn changeset`) before merge. ## Security & Configuration @@ -50,6 +50,9 @@ - `.cursor/rules/monorepo-organization.mdc`: Imports/exports, package boundaries, Turbo/Vite/TS paths. - `.cursor/rules/versioning-with-npm.mdc`: npm CLI version bumps (patch-first), CI publishes on merge. +## AI Agent Workflows +- `:researcher` - Investigate bugs/implementation questions and produce evidence-backed recommendations aligned with the constitution and tech stack. + When to review before starting work - Building/refactoring UI components: react-typescript-patterns + ui-component-patterns. - Form-aware components or validation: form-component-patterns. diff --git a/apps/docs/src/examples/middleware-example.tsx b/apps/docs/src/examples/middleware-example.tsx index 9451ede9..7faf2ed0 100644 --- a/apps/docs/src/examples/middleware-example.tsx +++ b/apps/docs/src/examples/middleware-example.tsx @@ -1,8 +1,8 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { TextField } from '@lambdacurry/forms/remix-hook-form'; +import type { ActionFunctionArgs } from 'react-router'; // Example of using the new middleware feature in remix-hook-form v7.0.0 import { Form } from 'react-router'; -import type { ActionFunctionArgs } from 'react-router'; import { RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { getValidatedFormData } from 'remix-hook-form/middleware'; import * as zod from 'zod'; diff --git a/apps/docs/src/lib/storybook/react-router-stub.tsx b/apps/docs/src/lib/storybook/react-router-stub.tsx index fd50abbf..f63c7087 100644 --- a/apps/docs/src/lib/storybook/react-router-stub.tsx +++ b/apps/docs/src/lib/storybook/react-router-stub.tsx @@ -2,10 +2,10 @@ import type { Decorator } from '@storybook/react-vite'; import type { ComponentType } from 'react'; import { type ActionFunction, + createRoutesStub, type LinksFunction, type LoaderFunction, type MetaFunction, - createRoutesStub, } from 'react-router'; export interface StubRouteObject { diff --git a/apps/docs/src/remix-hook-form/calendar-with-month-year-select.stories.tsx b/apps/docs/src/remix-hook-form/calendar-with-month-year-select.stories.tsx index 9c8859ed..79846b77 100644 --- a/apps/docs/src/remix-hook-form/calendar-with-month-year-select.stories.tsx +++ b/apps/docs/src/remix-hook-form/calendar-with-month-year-select.stories.tsx @@ -7,7 +7,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from '@storybook/test'; import * as React from 'react'; import { type ActionFunctionArgs, Form, useFetcher } from 'react-router'; -import { RemixFormProvider, createFormData, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { createFormData, getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/checkbox-custom.stories.tsx b/apps/docs/src/remix-hook-form/checkbox-custom.stories.tsx index 4e2c59c5..5f1ecc56 100644 --- a/apps/docs/src/remix-hook-form/checkbox-custom.stories.tsx +++ b/apps/docs/src/remix-hook-form/checkbox-custom.stories.tsx @@ -8,7 +8,7 @@ import { expect, userEvent, within } from '@storybook/test'; import type * as React from 'react'; import type { ActionFunctionArgs } from 'react-router'; import { useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/checkbox-list.stories.tsx b/apps/docs/src/remix-hook-form/checkbox-list.stories.tsx index 91fcd716..9adbba17 100644 --- a/apps/docs/src/remix-hook-form/checkbox-list.stories.tsx +++ b/apps/docs/src/remix-hook-form/checkbox-list.stories.tsx @@ -5,7 +5,7 @@ import { Button } from '@lambdacurry/forms/ui/button'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, type within } from '@storybook/test'; import { type ActionFunctionArgs, Form, useFetcher } from 'react-router'; -import { RemixFormProvider, createFormData, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { createFormData, getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/checkbox.stories.tsx b/apps/docs/src/remix-hook-form/checkbox.stories.tsx index 7f8c93c2..6aaae0be 100644 --- a/apps/docs/src/remix-hook-form/checkbox.stories.tsx +++ b/apps/docs/src/remix-hook-form/checkbox.stories.tsx @@ -4,7 +4,7 @@ import { Button } from '@lambdacurry/forms/ui/button'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from '@storybook/test'; import { type ActionFunctionArgs, useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/data-table/data-table-client-side.stories.tsx b/apps/docs/src/remix-hook-form/data-table/data-table-client-side.stories.tsx index 9b9f8191..4a4659ac 100644 --- a/apps/docs/src/remix-hook-form/data-table/data-table-client-side.stories.tsx +++ b/apps/docs/src/remix-hook-form/data-table/data-table-client-side.stories.tsx @@ -4,14 +4,14 @@ import { columnConfigs, columns } from './data-table-stories.components'; import { DataTable, DataTableFilter, - type MockIssue, - type OnChangeFn, - type PaginationState, - type SortingState, getCoreRowModel, getPaginationRowModel, getSortedRowModel, + type MockIssue, mockDatabase, + type OnChangeFn, + type PaginationState, + type SortingState, useDataTableFilters, useFilterSync, useReactTable, diff --git a/apps/docs/src/remix-hook-form/data-table/data-table-router-form.stories.tsx b/apps/docs/src/remix-hook-form/data-table/data-table-router-form.stories.tsx index 32a0f3ef..04e72c38 100644 --- a/apps/docs/src/remix-hook-form/data-table/data-table-router-form.stories.tsx +++ b/apps/docs/src/remix-hook-form/data-table/data-table-router-form.stories.tsx @@ -302,8 +302,7 @@ export default meta; type Story = StoryObj; export const Default: Story = { - // biome-ignore lint/suspicious/noExplicitAny: - args: {} as any, // Args for DataTableRouterForm if needed, handled by Example component + args: {} satisfies Record, // Args for DataTableRouterForm if needed, handled by Example component render: () => , parameters: { docs: { diff --git a/apps/docs/src/remix-hook-form/data-table/data-table-server-driven.stories.tsx b/apps/docs/src/remix-hook-form/data-table/data-table-server-driven.stories.tsx index f5d93200..9ae7162e 100644 --- a/apps/docs/src/remix-hook-form/data-table/data-table-server-driven.stories.tsx +++ b/apps/docs/src/remix-hook-form/data-table/data-table-server-driven.stories.tsx @@ -3,20 +3,20 @@ import { useMemo } from 'react'; import { type LoaderFunctionArgs, useLoaderData, useSearchParams } from 'react-router'; import { columnConfigs, columns } from './data-table-stories.components'; import { + calculateFacetedCounts, type DataResponse, DataTable, DataTableFilter, - type MockIssue, - type OnChangeFn, - type PaginationState, - type SortingState, - calculateFacetedCounts, dataTableRouterParsers, filtersArraySchema, getCoreRowModel, getPaginationRowModel, getSortedRowModel, + type MockIssue, mockDatabase, + type OnChangeFn, + type PaginationState, + type SortingState, useDataTableFilters, useFilterSync, useReactTable, diff --git a/apps/docs/src/remix-hook-form/data-table/data-table-stories.components.tsx b/apps/docs/src/remix-hook-form/data-table/data-table-stories.components.tsx index 22e2de2e..3b2aa6f4 100644 --- a/apps/docs/src/remix-hook-form/data-table/data-table-stories.components.tsx +++ b/apps/docs/src/remix-hook-form/data-table/data-table-stories.components.tsx @@ -1,16 +1,16 @@ import type { ColumnDef } from '@tanstack/react-table'; import { + assigneeOptions, CalendarIcon, CheckCircledIcon, + createColumnConfigHelper, DataTableColumnHeader, type MockIssue, PersonIcon, - StarIcon, - TextIcon, - assigneeOptions, - createColumnConfigHelper, priorityOptions, + StarIcon, statusOptions, + TextIcon, } from './data-table-stories.helpers'; // --- Column Configuration --- diff --git a/apps/docs/src/remix-hook-form/data-table/data-table-stories.helpers.ts b/apps/docs/src/remix-hook-form/data-table/data-table-stories.helpers.ts index 39aaa6c8..163e0303 100644 --- a/apps/docs/src/remix-hook-form/data-table/data-table-stories.helpers.ts +++ b/apps/docs/src/remix-hook-form/data-table/data-table-stories.helpers.ts @@ -218,16 +218,16 @@ export const priorityOptions = [ // --- Shared Imports (for re-export) --- export { dataTableRouterParsers } from '@lambdacurry/forms/remix-hook-form/data-table-router-parsers'; +export { DataTable } from '@lambdacurry/forms/ui/data-table/data-table'; +export { DataTableColumnHeader } from '@lambdacurry/forms/ui/data-table/data-table-column-header'; export { DataTableFilter } from '@lambdacurry/forms/ui/data-table-filter/components/data-table-filter'; export { createColumnConfigHelper } from '@lambdacurry/forms/ui/data-table-filter/core/filters'; export { useDataTableFilters } from '@lambdacurry/forms/ui/data-table-filter/hooks/use-data-table-filters'; -export { DataTable } from '@lambdacurry/forms/ui/data-table/data-table'; -export { DataTableColumnHeader } from '@lambdacurry/forms/ui/data-table/data-table-column-header'; export type { FiltersState } from '@lambdacurry/forms/ui/utils/filters'; export { filtersArraySchema } from '@lambdacurry/forms/ui/utils/filters'; export { useFilterSync } from '@lambdacurry/forms/ui/utils/use-filter-sync'; export { CalendarIcon, CheckCircledIcon, PersonIcon, StarIcon, TextIcon } from '@radix-ui/react-icons'; -export type { ColumnDef, PaginationState, SortingState, OnChangeFn } from '@tanstack/react-table'; +export type { ColumnDef, OnChangeFn, PaginationState, SortingState } from '@tanstack/react-table'; export { getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from '@tanstack/react-table'; export { useMemo } from 'react'; export { type LoaderFunctionArgs, useLoaderData, useLocation, useNavigate, useSearchParams } from 'react-router'; diff --git a/apps/docs/src/remix-hook-form/date-picker.stories.tsx b/apps/docs/src/remix-hook-form/date-picker.stories.tsx index e2a4cd82..f7d33034 100644 --- a/apps/docs/src/remix-hook-form/date-picker.stories.tsx +++ b/apps/docs/src/remix-hook-form/date-picker.stories.tsx @@ -4,7 +4,7 @@ import { Button } from '@lambdacurry/forms/ui/button'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from '@storybook/test'; import { type ActionFunctionArgs, Form, useFetcher } from 'react-router'; -import { RemixFormProvider, createFormData, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { createFormData, getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/form-error-basic.stories.tsx b/apps/docs/src/remix-hook-form/form-error-basic.stories.tsx index be75db2e..2ea4bf6a 100644 --- a/apps/docs/src/remix-hook-form/form-error-basic.stories.tsx +++ b/apps/docs/src/remix-hook-form/form-error-basic.stories.tsx @@ -4,7 +4,7 @@ import { Button } from '@lambdacurry/forms/ui/button'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from '@storybook/test'; import { type ActionFunctionArgs, useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/form-error-custom.stories.tsx b/apps/docs/src/remix-hook-form/form-error-custom.stories.tsx index 561dca6c..a002089a 100644 --- a/apps/docs/src/remix-hook-form/form-error-custom.stories.tsx +++ b/apps/docs/src/remix-hook-form/form-error-custom.stories.tsx @@ -5,7 +5,7 @@ import { Button } from '@lambdacurry/forms/ui/button'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from '@storybook/test'; import { type ActionFunctionArgs, useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; @@ -19,7 +19,14 @@ type FormData = z.infer; // Custom error message component with icon const AlertErrorMessage = (props: React.ComponentProps) => (
- + + Error ({ @@ -30,14 +31,14 @@ const TestFormWithError = ({ }: { initialErrors?: Record; formErrorName?: string; - customComponents?: any; + customComponents?: { FormMessage?: React.ComponentType>> }; className?: string; }) => { const mockFetcher = { data: { errors: initialErrors }, state: 'idle' as const, submit: jest.fn(), - Form: 'form' as any, + Form: 'form' as ElementType, }; mockUseFetcher.mockReturnValue(mockFetcher); @@ -142,7 +143,7 @@ describe('FormError Component', () => { describe('Component Customization', () => { it('uses custom FormMessage component when provided', () => { - const CustomFormMessage = ({ children, ...props }: any) => ( + const CustomFormMessage = ({ children, ...props }: PropsWithChildren>) => (
Custom: {children}
@@ -216,7 +217,7 @@ describe('FormError Component', () => { }, state: 'idle' as const, submit: jest.fn(), - Form: 'form' as any, + Form: 'form' as ElementType, }; mockUseFetcher.mockReturnValue(mockFetcher); @@ -314,7 +315,7 @@ describe('FormError Component', () => { it('does not re-render unnecessarily when unrelated form state changes', () => { const renderSpy = jest.fn(); - const CustomFormMessage = ({ children, ...props }: any) => { + const CustomFormMessage = ({ children, ...props }: PropsWithChildren>) => { renderSpy(); return
{children}
; }; @@ -344,7 +345,7 @@ describe('FormError Integration Tests', () => { data: null, state: 'idle' as const, submit: jest.fn(), - Form: 'form' as any, + Form: 'form' as ElementType, }; mockUseFetcher.mockReturnValue(mockFetcher); diff --git a/apps/docs/src/remix-hook-form/otp-input.stories.tsx b/apps/docs/src/remix-hook-form/otp-input.stories.tsx index 015461c1..83465439 100644 --- a/apps/docs/src/remix-hook-form/otp-input.stories.tsx +++ b/apps/docs/src/remix-hook-form/otp-input.stories.tsx @@ -4,7 +4,7 @@ import { Button } from '@lambdacurry/forms/ui/button'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from '@storybook/test'; import { type ActionFunctionArgs, Form, useFetcher } from 'react-router'; -import { RemixFormProvider, createFormData, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { createFormData, getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/password-field.stories.tsx b/apps/docs/src/remix-hook-form/password-field.stories.tsx index 5d91b126..bf3dc4c6 100644 --- a/apps/docs/src/remix-hook-form/password-field.stories.tsx +++ b/apps/docs/src/remix-hook-form/password-field.stories.tsx @@ -5,7 +5,7 @@ import type { Meta, StoryContext, StoryObj } from '@storybook/react-vite'; import { expect, userEvent } from '@storybook/test'; import { useRef } from 'react'; import { type ActionFunctionArgs, useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/phone-input.stories.tsx b/apps/docs/src/remix-hook-form/phone-input.stories.tsx index 82b74fea..8a593f73 100644 --- a/apps/docs/src/remix-hook-form/phone-input.stories.tsx +++ b/apps/docs/src/remix-hook-form/phone-input.stories.tsx @@ -4,7 +4,7 @@ import { Button } from '@lambdacurry/forms/ui/button'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from '@storybook/test'; import { type ActionFunctionArgs, useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/phone-input.test.tsx b/apps/docs/src/remix-hook-form/phone-input.test.tsx index fe53d9c1..ba92e91a 100644 --- a/apps/docs/src/remix-hook-form/phone-input.test.tsx +++ b/apps/docs/src/remix-hook-form/phone-input.test.tsx @@ -6,6 +6,7 @@ import userEvent from '@testing-library/user-event'; import { useFetcher } from 'react-router'; import { RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; +import type { ElementType, PropsWithChildren } from 'react'; // Mock useFetcher jest.mock('react-router', () => ({ @@ -28,13 +29,13 @@ const TestPhoneInputForm = ({ customComponents = {}, }: { initialErrors?: Record; - customComponents?: any; + customComponents?: { FormMessage?: React.ComponentType>> }; }) => { const mockFetcher = { data: { errors: initialErrors }, state: 'idle' as const, submit: jest.fn(), - Form: 'form' as any, + Form: 'form' as ElementType, }; mockUseFetcher.mockReturnValue(mockFetcher); @@ -151,7 +152,7 @@ describe('PhoneInput Component', () => { describe('Component Customization', () => { it('uses custom FormMessage component when provided', () => { - const CustomFormMessage = ({ children, ...props }: any) => ( + const CustomFormMessage = ({ children, ...props }: PropsWithChildren>) => (
Custom: {children}
diff --git a/apps/docs/src/remix-hook-form/radio-group-custom.stories.tsx b/apps/docs/src/remix-hook-form/radio-group-custom.stories.tsx index 2b560a7e..2f6b7171 100644 --- a/apps/docs/src/remix-hook-form/radio-group-custom.stories.tsx +++ b/apps/docs/src/remix-hook-form/radio-group-custom.stories.tsx @@ -8,7 +8,7 @@ import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from '@storybook/test'; import { type ActionFunctionArgs, Form, useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/radio-group.stories.tsx b/apps/docs/src/remix-hook-form/radio-group.stories.tsx index e46a884b..b2a825d8 100644 --- a/apps/docs/src/remix-hook-form/radio-group.stories.tsx +++ b/apps/docs/src/remix-hook-form/radio-group.stories.tsx @@ -5,7 +5,7 @@ import { Button } from '@lambdacurry/forms/ui/button'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from '@storybook/test'; import { type ActionFunctionArgs, Form, useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/scroll-to-error.stories.tsx b/apps/docs/src/remix-hook-form/scroll-to-error.stories.tsx index 5cf028ae..aba51c87 100644 --- a/apps/docs/src/remix-hook-form/scroll-to-error.stories.tsx +++ b/apps/docs/src/remix-hook-form/scroll-to-error.stories.tsx @@ -10,7 +10,7 @@ import { Textarea } from '@lambdacurry/forms/remix-hook-form/textarea'; import { Button } from '@lambdacurry/forms/ui/button'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { type ActionFunctionArgs, useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm, useRemixFormContext } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm, useRemixFormContext } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/select-custom.stories.tsx b/apps/docs/src/remix-hook-form/select-custom.stories.tsx index c7a39fd1..d91b1a4f 100644 --- a/apps/docs/src/remix-hook-form/select-custom.stories.tsx +++ b/apps/docs/src/remix-hook-form/select-custom.stories.tsx @@ -6,7 +6,7 @@ import { expect, userEvent, within } from '@storybook/test'; import clsx from 'clsx'; import * as React from 'react'; import { type ActionFunctionArgs, useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/select.stories.tsx b/apps/docs/src/remix-hook-form/select.stories.tsx index d5296b93..d680d6d0 100644 --- a/apps/docs/src/remix-hook-form/select.stories.tsx +++ b/apps/docs/src/remix-hook-form/select.stories.tsx @@ -6,7 +6,7 @@ import { US_STATES } from '@lambdacurry/forms/ui/data/us-states'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from '@storybook/test'; import { type ActionFunctionArgs, useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/switch-custom.stories.tsx b/apps/docs/src/remix-hook-form/switch-custom.stories.tsx index 356d7042..dd0f9d6b 100644 --- a/apps/docs/src/remix-hook-form/switch-custom.stories.tsx +++ b/apps/docs/src/remix-hook-form/switch-custom.stories.tsx @@ -8,7 +8,7 @@ import { expect, userEvent, within } from '@storybook/test'; import type * as React from 'react'; import type { ActionFunctionArgs } from 'react-router'; import { useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/switch.stories.tsx b/apps/docs/src/remix-hook-form/switch.stories.tsx index cd9eaedd..1ae13caf 100644 --- a/apps/docs/src/remix-hook-form/switch.stories.tsx +++ b/apps/docs/src/remix-hook-form/switch.stories.tsx @@ -4,7 +4,7 @@ import { Button } from '@lambdacurry/forms/ui/button'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from '@storybook/test'; import { type ActionFunctionArgs, useFetcher } from 'react-router'; -import { RemixFormProvider, createFormData, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { createFormData, getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/text-field-custom.stories.tsx b/apps/docs/src/remix-hook-form/text-field-custom.stories.tsx index 1e0edc9b..79556c7b 100644 --- a/apps/docs/src/remix-hook-form/text-field-custom.stories.tsx +++ b/apps/docs/src/remix-hook-form/text-field-custom.stories.tsx @@ -6,7 +6,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { expect, userEvent, within } from '@storybook/test'; import type * as React from 'react'; import { type ActionFunctionArgs, useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/text-field.stories.tsx b/apps/docs/src/remix-hook-form/text-field.stories.tsx index cba38352..50051af9 100644 --- a/apps/docs/src/remix-hook-form/text-field.stories.tsx +++ b/apps/docs/src/remix-hook-form/text-field.stories.tsx @@ -5,7 +5,7 @@ import type { Meta, StoryContext, StoryObj } from '@storybook/react-vite'; import { expect, userEvent } from '@storybook/test'; import { useRef } from 'react'; import { type ActionFunctionArgs, useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/textarea-custom.stories.tsx b/apps/docs/src/remix-hook-form/textarea-custom.stories.tsx index 1395ecf4..b774ae18 100644 --- a/apps/docs/src/remix-hook-form/textarea-custom.stories.tsx +++ b/apps/docs/src/remix-hook-form/textarea-custom.stories.tsx @@ -7,7 +7,7 @@ import { expect, userEvent, within } from '@storybook/test'; import * as React from 'react'; import type { ActionFunctionArgs } from 'react-router'; import { useFetcher } from 'react-router'; -import { RemixFormProvider, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/apps/docs/src/remix-hook-form/textarea.stories.tsx b/apps/docs/src/remix-hook-form/textarea.stories.tsx index d8995279..af145ccd 100644 --- a/apps/docs/src/remix-hook-form/textarea.stories.tsx +++ b/apps/docs/src/remix-hook-form/textarea.stories.tsx @@ -4,7 +4,7 @@ import { Button } from '@lambdacurry/forms/ui/button'; import type { Meta, StoryContext, StoryObj } from '@storybook/react-vite'; import { expect, userEvent } from '@storybook/test'; import { type ActionFunctionArgs, useFetcher } from 'react-router'; -import { RemixFormProvider, createFormData, getValidatedFormData, useRemixForm } from 'remix-hook-form'; +import { createFormData, getValidatedFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form'; import { z } from 'zod'; import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; diff --git a/biome.json b/biome.json index 00b0801c..4de8723b 100644 --- a/biome.json +++ b/biome.json @@ -1,12 +1,15 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }, - "files": { "ignoreUnknown": false, "ignore": [".turbo", "yarn.lock", "dist", "node_modules", "storybook-static"] }, - "organizeImports": { "enabled": true }, + "files": { + "ignoreUnknown": false, + "includes": ["**", "!**/.turbo", "!**/yarn.lock", "!**/dist", "!**/node_modules", "!**/storybook-static"] + }, + "assist": { "actions": { "source": { "organizeImports": "off" } } }, "formatter": { "enabled": true, "formatWithErrors": false, @@ -22,38 +25,35 @@ "linter": { "enabled": true, "rules": { - "all": true, "style": { - "all": true, "useBlockStatements": "off", "useNamingConvention": "off", "noImplicitBoolean": "off", "noDefaultExport": "off", "noUnusedTemplateLiteral": "off", - "useFilenamingConvention": "off", - "noNamespaceImport": "off" + "useFilenamingConvention": "off" }, "complexity": { - "all": true, "noForEach": "off", "useLiteralKeys": "off" }, "performance": { - "all": true, "noAccumulatingSpread": "off", "noReExportAll": "off", - "noBarrelFile": "off" + "noBarrelFile": "off", + "noNamespaceImport": "off" }, "suspicious": { - "noConsoleLog": "off", "noConsole": "off", - "noReactSpecificProps": "off" + "noReactSpecificProps": "off", + "noUnknownAtRules": "off" }, "correctness": { - "all": true, "noNodejsModules": "off", "noUndeclaredDependencies": "off", - "useImportExtensions": "off" + "useImportExtensions": "off", + "noUnusedVariables": "off", + "useUniqueElementIds": "off" } } } diff --git a/package.json b/package.json index 609afa67..76c33ddc 100644 --- a/package.json +++ b/package.json @@ -13,14 +13,14 @@ "serve": "turbo run serve", "test": "turbo run test", "clean": "find . -name '.turbo' -type d -prune -exec rm -rf {} + && find . -name 'node_modules' -type d -prune -exec rm -rf {} + && find . -name 'yarn.lock' -type f -delete", - "format-and-lint": "biome check .", - "format-and-lint:fix": "biome check . --write", + "lint": "biome check .", + "lint:fix": "biome check . --write", "prerelease": "turbo run build", "release": "changeset publish", "build-storybook": "turbo run build-storybook" }, "devDependencies": { - "@biomejs/biome": "^1.9.4", + "@biomejs/biome": "2.2.4", "@playwright/test": "^1.54.2", "@types/react-dom": "^19", "turbo": "^2.3.3" diff --git a/packages/components/package.json b/packages/components/package.json index 46964cd6..ec77cf93 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -26,12 +26,14 @@ "import": "./dist/ui/*.js" } }, - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "prepublishOnly": "yarn run build", "build": "vite build", "lint": "biome check .", - "lint:fix": "biome check --apply .", + "lint:fix": "biome check . --write", "type-check": "tsc --noEmit" }, "peerDependencies": { diff --git a/packages/components/src/data-table/index.ts b/packages/components/src/data-table/index.ts index c5c7803c..2020daf8 100644 --- a/packages/components/src/data-table/index.ts +++ b/packages/components/src/data-table/index.ts @@ -1,14 +1,11 @@ // Re-export all data table filter components -export { DataTableFilter, useDataTableFilters } from '../ui/data-table-filter'; - -// Re-export core data table functionality -export { createColumnConfigHelper } from '../ui/data-table-filter/core/filters'; - -// Re-export utilities -export { useFilterSync } from '../ui/utils/use-filter-sync'; // Re-export all core data table components export * from '../ui/data-table'; - +export { DataTableFilter, useDataTableFilters } from '../ui/data-table-filter'; +// Re-export core data table functionality +export { createColumnConfigHelper } from '../ui/data-table-filter/core/filters'; // Re-export types if needed export type * from '../ui/data-table-filter/core/types'; +// Re-export utilities +export { useFilterSync } from '../ui/utils/use-filter-sync'; diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 7adcf11f..32643cb1 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -1,12 +1,10 @@ // Main exports from both remix-hook-form and ui directories -// Add scroll-to-error utilities -export { scrollToFirstError } from './utils/scrollToError'; -export type { ScrollToErrorOptions } from './utils/scrollToError'; - // Export all components from remix-hook-form export * from './remix-hook-form'; - // Explicitly export Textarea from both locations to handle naming conflicts // The remix-hook-form Textarea is a form-aware wrapper export { Textarea as TextareaField } from './remix-hook-form/textarea'; +export type { ScrollToErrorOptions } from './utils/scrollToError'; +// Add scroll-to-error utilities +export { scrollToFirstError } from './utils/scrollToError'; diff --git a/packages/components/src/remix-hook-form/data-table-router-form.tsx b/packages/components/src/remix-hook-form/data-table-router-form.tsx index 308e15f1..39a61d85 100644 --- a/packages/components/src/remix-hook-form/data-table-router-form.tsx +++ b/packages/components/src/remix-hook-form/data-table-router-form.tsx @@ -1,7 +1,5 @@ import { type ColumnDef, - // type ColumnFilter, // No longer directly used for state.columnFilters - type VisibilityState, flexRender, getCoreRowModel, getFacetedRowModel, @@ -10,6 +8,8 @@ import { getPaginationRowModel, getSortedRowModel, useReactTable, + // type ColumnFilter, // No longer directly used for state.columnFilters + type VisibilityState, } from '@tanstack/react-table'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useNavigation } from 'react-router'; @@ -17,17 +17,15 @@ import { RemixFormProvider, useRemixForm } from 'remix-hook-form'; // import { z } from 'zod'; // Schema is now more for URL state structure import { DataTablePagination } from '../ui/data-table/data-table-pagination'; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table'; -import { DataTableRouterToolbar } from './data-table-router-toolbar'; - // Bazza UI imports - assuming types for ColumnConfig and output of useDataTableFilters // For now, using 'any' for some bazza types if not precisely known. import { useDataTableFilters, // The hook from bazza/ui // createColumnConfigHelper, // Assume columnsConfig is pre-built and passed in } from '../ui/data-table-filter'; // Adjusted path - +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table'; import type { BazzaFiltersState, DataTableRouterState } from './data-table-router-parsers'; +import { DataTableRouterToolbar } from './data-table-router-toolbar'; import { getDefaultDataTableState, useDataTableUrlState } from './use-data-table-url-state'; // dataTableSchema can remain to validate the shape of URL params if desired, but RemixForm doesn't use a resolver here. @@ -38,11 +36,11 @@ export interface DataTableRouterFormProps { pageCount?: number; defaultStateValues?: Partial; // New prop for Bazza UI filter configurations - // This should be typed according to bazza/ui's ColumnConfig type (e.g., from createColumnConfigHelper(...).build()) - filterColumnConfigs: any[]; // Placeholder type for Bazza UI ColumnConfig[] + // Shape is intentionally loose to allow passing configs from various sources without tight coupling + filterColumnConfigs: unknown[]; // Props for server-fetched options/faceted data for bazza/ui, if needed for server strategy - dtfOptions?: Record; - dtfFacetedData?: Record; + dtfOptions?: unknown; + dtfFacetedData?: unknown; } export function DataTableRouterForm({ @@ -71,9 +69,10 @@ export function DataTableRouterForm({ } = useDataTableFilters({ strategy: 'server', data: data, - columnsConfig: filterColumnConfigs, - options: dtfOptions, - faceted: dtfFacetedData, + // Cast to the exact parameter type to preserve strong typing without using `any` + columnsConfig: filterColumnConfigs as Parameters[0]['columnsConfig'], + options: dtfOptions as Parameters[0]['options'], + faceted: dtfFacetedData as Parameters[0]['faceted'], filters: urlState.filters, // Use URL filters as the source of truth onFiltersChange: (newFilters) => { // Update URL state when filters change diff --git a/packages/components/src/remix-hook-form/data-table-router-toolbar.tsx b/packages/components/src/remix-hook-form/data-table-router-toolbar.tsx index 6ace1c29..16c8f64c 100644 --- a/packages/components/src/remix-hook-form/data-table-router-toolbar.tsx +++ b/packages/components/src/remix-hook-form/data-table-router-toolbar.tsx @@ -3,8 +3,9 @@ import type { Table } from '@tanstack/react-table'; import { type ChangeEvent, useCallback } from 'react'; import { Button } from '../ui/button'; -import { DataTableFilter } from '../ui/data-table-filter'; import { DataTableViewOptions } from '../ui/data-table/data-table-view-options'; +import { DataTableFilter } from '../ui/data-table-filter'; +import type { Column, DataTableFilterActions, FilterStrategy } from '../ui/data-table-filter/core/types'; import type { BazzaFiltersState } from './data-table-router-parsers'; import { TextField } from './text-field'; @@ -15,10 +16,10 @@ export interface DataTableRouterToolbarProps { onResetFiltersAndSearch: () => void; hasActiveFiltersOrSearch: boolean; - dtfColumns: any[]; + dtfColumns: Column[]; dtfFilters: BazzaFiltersState; - dtfActions: any; - dtfStrategy: 'client' | 'server'; + dtfActions: DataTableFilterActions; + dtfStrategy: FilterStrategy; } export function DataTableRouterToolbar({ diff --git a/packages/components/src/remix-hook-form/hooks/useScrollToErrorOnSubmit.ts b/packages/components/src/remix-hook-form/hooks/useScrollToErrorOnSubmit.ts index 8baee846..2ec2f8eb 100644 --- a/packages/components/src/remix-hook-form/hooks/useScrollToErrorOnSubmit.ts +++ b/packages/components/src/remix-hook-form/hooks/useScrollToErrorOnSubmit.ts @@ -1,7 +1,7 @@ import { useEffect, useMemo } from 'react'; import type { FieldValues } from 'react-hook-form'; -import { useRemixFormContext } from 'remix-hook-form'; import type { UseRemixFormReturn } from 'remix-hook-form'; +import { useRemixFormContext } from 'remix-hook-form'; import { type ScrollToErrorOptions, scrollToFirstError } from '../../utils/scrollToError'; export interface UseScrollToErrorOnSubmitOptions extends ScrollToErrorOptions { diff --git a/packages/components/src/remix-hook-form/index.ts b/packages/components/src/remix-hook-form/index.ts index 7ac43f64..7b095538 100644 --- a/packages/components/src/remix-hook-form/index.ts +++ b/packages/components/src/remix-hook-form/index.ts @@ -1,25 +1,25 @@ // Add scroll-to-error functionality -export * from './hooks/useScrollToErrorOnSubmit'; -export * from './components/ScrollToErrorOnSubmit'; +export * from './canada-province-select'; // Keep all existing exports export * from './checkbox'; +export * from './components/ScrollToErrorOnSubmit'; +export * from './data-table-router-form'; +export * from './data-table-router-parsers'; +export * from './data-table-router-toolbar'; +export * from './date-picker'; export * from './form'; export * from './form-error'; -export * from './date-picker'; -export * from './phone-input'; -export * from './text-field'; +export * from './hooks/useScrollToErrorOnSubmit'; +export * from './otp-input'; export * from './password-field'; +export * from './phone-input'; export * from './radio-group'; export * from './radio-group-item'; +export * from './select'; export * from './switch'; +export * from './text-field'; export * from './textarea'; -export * from './otp-input'; -export * from './data-table-router-form'; -export * from './data-table-router-parsers'; -export * from './data-table-router-toolbar'; -export * from './use-data-table-url-state'; -export * from './select'; export * from './us-state-select'; -export * from './canada-province-select'; +export * from './use-data-table-url-state'; export * from './hidden-field'; diff --git a/packages/components/src/remix-hook-form/password-field.tsx b/packages/components/src/remix-hook-form/password-field.tsx index 58b926a2..c8f2b0ed 100644 --- a/packages/components/src/remix-hook-form/password-field.tsx +++ b/packages/components/src/remix-hook-form/password-field.tsx @@ -1,12 +1,11 @@ import type * as React from 'react'; +import { useRemixFormContext } from 'remix-hook-form'; import { PasswordField as BasePasswordField, type PasswordInputProps as BasePasswordFieldProps, } from '../ui/password-field'; import { FormControl, FormDescription, FormLabel, FormMessage } from './form'; -import { useRemixFormContext } from 'remix-hook-form'; - export type PasswordFieldProps = Omit; export const PasswordField = function RemixPasswordField( diff --git a/packages/components/src/remix-hook-form/phone-input.tsx b/packages/components/src/remix-hook-form/phone-input.tsx index c3b27fd2..b2b238f5 100644 --- a/packages/components/src/remix-hook-form/phone-input.tsx +++ b/packages/components/src/remix-hook-form/phone-input.tsx @@ -1,12 +1,11 @@ import type * as React from 'react'; +import { useRemixFormContext } from 'remix-hook-form'; import { PhoneInputField as BasePhoneInputField, type PhoneInputFieldProps as BasePhoneInputFieldProps, } from '../ui/phone-input-field'; import { FormControl, FormDescription, FormLabel, FormMessage } from './form'; -import { useRemixFormContext } from 'remix-hook-form'; - export type PhoneInputProps = Omit; export const PhoneInput = function RemixPhoneInput(props: PhoneInputProps & { ref?: React.Ref }) { diff --git a/packages/components/src/remix-hook-form/text-field.tsx b/packages/components/src/remix-hook-form/text-field.tsx index 514abb1e..82ee172f 100644 --- a/packages/components/src/remix-hook-form/text-field.tsx +++ b/packages/components/src/remix-hook-form/text-field.tsx @@ -1,9 +1,8 @@ import type * as React from 'react'; +import { useRemixFormContext } from 'remix-hook-form'; import { TextField as BaseTextField, type TextInputProps as BaseTextFieldProps } from '../ui/text-field'; import { FormControl, FormDescription, FormLabel, FormMessage } from './form'; -import { useRemixFormContext } from 'remix-hook-form'; - export type TextFieldProps = Omit; export const TextField = function RemixTextField(props: TextFieldProps & { ref?: React.Ref }) { diff --git a/packages/components/src/remix-hook-form/use-data-table-url-state.ts b/packages/components/src/remix-hook-form/use-data-table-url-state.ts index 93cf5165..f3f3e3df 100644 --- a/packages/components/src/remix-hook-form/use-data-table-url-state.ts +++ b/packages/components/src/remix-hook-form/use-data-table-url-state.ts @@ -25,7 +25,7 @@ export function useDataTableUrlState() { }; // Function to update URL search parameters - // biome-ignore lint/correctness/useExhaustiveDependencies: + // biome-ignore lint/correctness/useExhaustiveDependencies: setSearchParams is stable; urlState is read at call time const setUrlState = useCallback( (newState: Partial) => { const updatedState = { ...urlState, ...newState }; diff --git a/packages/components/src/ui/badge.tsx b/packages/components/src/ui/badge.tsx index b329593d..42dc12a3 100644 --- a/packages/components/src/ui/badge.tsx +++ b/packages/components/src/ui/badge.tsx @@ -1,4 +1,4 @@ -import { type VariantProps, cva } from 'class-variance-authority'; +import { cva, type VariantProps } from 'class-variance-authority'; import type * as React from 'react'; import { cn } from './utils'; diff --git a/packages/components/src/ui/button.tsx b/packages/components/src/ui/button.tsx index 7ec32f71..4780b5a5 100644 --- a/packages/components/src/ui/button.tsx +++ b/packages/components/src/ui/button.tsx @@ -1,5 +1,5 @@ import { Slot } from '@radix-ui/react-slot'; -import { type VariantProps, cva } from 'class-variance-authority'; +import { cva, type VariantProps } from 'class-variance-authority'; import type * as React from 'react'; import type { ButtonHTMLAttributes } from 'react'; import { cn } from './utils'; diff --git a/packages/components/src/ui/calendar.tsx b/packages/components/src/ui/calendar.tsx index 8feb8baa..f960b2d1 100644 --- a/packages/components/src/ui/calendar.tsx +++ b/packages/components/src/ui/calendar.tsx @@ -72,9 +72,11 @@ function Calendar({ ...classNames, }} components={{ + // biome-ignore lint/correctness/noNestedComponentDefinitions: Inline override required by DayPicker components API Root: ({ className, rootRef, ...props }) => { return
; }, + // biome-ignore lint/correctness/noNestedComponentDefinitions: Inline override required by DayPicker components API Chevron: ({ className, orientation, ...props }) => { if (orientation === 'left') { return ; @@ -87,6 +89,7 @@ function Calendar({ return ; }, DayButton: CalendarDayButton, + // biome-ignore lint/correctness/noNestedComponentDefinitions: Inline override required by DayPicker components API WeekNumber: ({ children, ...props }) => { return ( diff --git a/packages/components/src/ui/data-table-filter/components/active-filters.tsx b/packages/components/src/ui/data-table-filter/components/active-filters.tsx index d4023084..ed5e660d 100644 --- a/packages/components/src/ui/data-table-filter/components/active-filters.tsx +++ b/packages/components/src/ui/data-table-filter/components/active-filters.tsx @@ -111,7 +111,7 @@ export function ActiveFiltersMobileContainer({ children }: { children: React.Rea }; // Set up ResizeObserver to monitor container size - // biome-ignore lint/correctness/useExhaustiveDependencies: + // biome-ignore lint/correctness/useExhaustiveDependencies: set once on mount; ref element is stable useEffect(() => { if (scrollContainerRef.current) { const resizeObserver = new ResizeObserver(() => { @@ -125,7 +125,7 @@ export function ActiveFiltersMobileContainer({ children }: { children: React.Rea }, []); // Update blur states when children change - // biome-ignore lint/correctness/useExhaustiveDependencies: + // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally depends on children only to recompute blur useEffect(() => { checkScroll(); }, [children]); diff --git a/packages/components/src/ui/data-table-filter/components/filter-selector.tsx b/packages/components/src/ui/data-table-filter/components/filter-selector.tsx index f9a66456..8b2ccf22 100644 --- a/packages/components/src/ui/data-table-filter/components/filter-selector.tsx +++ b/packages/components/src/ui/data-table-filter/components/filter-selector.tsx @@ -1,6 +1,5 @@ import { ArrowRightIcon, ChevronRightIcon, FilterIcon } from 'lucide-react'; -import { isValidElement, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import React from 'react'; +import React, { isValidElement, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Button } from '../../button'; import { Checkbox } from '../../checkbox'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '../../command'; @@ -209,8 +208,8 @@ function __QuickSearchFilters({ filters, columns, actions, - strategy, - locale = 'en', + strategy: _strategy, + locale: _locale = 'en', }: QuickSearchFiltersProps) { const cols = useMemo( () => columns.filter((c) => isAnyOf(c.type, ['option', 'multiOption'])), diff --git a/packages/components/src/ui/data-table-filter/components/filter-value.tsx b/packages/components/src/ui/data-table-filter/components/filter-value.tsx index 210b7702..2ca39ad0 100644 --- a/packages/components/src/ui/data-table-filter/components/filter-value.tsx +++ b/packages/components/src/ui/data-table-filter/components/filter-value.tsx @@ -1,7 +1,6 @@ -import { isEqual } from 'date-fns'; -import { format } from 'date-fns'; +import { format, isEqual } from 'date-fns'; import { Ellipsis } from 'lucide-react'; -import { type ElementType, cloneElement, isValidElement, memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { cloneElement, type ElementType, isValidElement, memo, useCallback, useEffect, useMemo, useState } from 'react'; import type { DateRange } from 'react-day-picker'; import { Button } from '../../button'; import { Calendar } from '../../calendar'; @@ -137,8 +136,8 @@ export function FilterValueDisplay({ export function FilterValueOptionDisplay({ filter, column, - actions, - locale = 'en', + actions: _actions, + locale: _locale = 'en', }: FilterValueDisplayProps) { const options = useMemo(() => column.getOptions(), [column]); const selected = options.filter((o) => filter?.values.includes(o.value)); @@ -183,8 +182,8 @@ export function FilterValueOptionDisplay({ export function FilterValueMultiOptionDisplay({ filter, column, - actions, - locale = 'en', + actions: _actions, + locale: _locale = 'en', }: FilterValueDisplayProps) { const options = useMemo(() => column.getOptions(), [column]); const selected = options.filter((o) => filter.values.includes(o.value)); @@ -247,9 +246,9 @@ function formatDateRange(start: Date | string | number, end: Date | string | num export function FilterValueDateDisplay({ filter, - column, - actions, - locale = 'en', + column: _column, + actions: _actions, + locale: _locale = 'en', }: FilterValueDisplayProps) { if (!filter) return null; if (filter.values.length === 0) return ; @@ -274,9 +273,9 @@ export function FilterValueDateDisplay({ export function FilterValueTextDisplay({ filter, - column, - actions, - locale = 'en', + column: _column, + actions: _actions, + locale: _locale = 'en', }: FilterValueDisplayProps) { if (!filter) return null; if (filter.values.length === 0 || filter.values[0].trim() === '') return ; @@ -288,9 +287,9 @@ export function FilterValueTextDisplay({ export function FilterValueNumberDisplay({ filter, - column, - actions, - locale = 'en', + column: _column, + actions: _actions, + locale: _locale = 'en', }: FilterValueDisplayProps) { if (!filter || !filter.values || filter.values.length === 0) return null; diff --git a/packages/components/src/ui/data-table-filter/core/filters.ts b/packages/components/src/ui/data-table-filter/core/filters.ts index b9e20a50..28b40a9f 100644 --- a/packages/components/src/ui/data-table-filter/core/filters.ts +++ b/packages/components/src/ui/data-table-filter/core/filters.ts @@ -16,7 +16,7 @@ import type { class ColumnConfigBuilder< TData, - TType extends ColumnDataType = any, + TType extends ColumnDataType = ColumnDataType, TVal = unknown, TId extends string = string, // Add TId generic > { @@ -33,15 +33,15 @@ class ColumnConfigBuilder< } id(value: TNewId): ColumnConfigBuilder { - const newInstance = this.clone() as any; // We'll refine this - newInstance.config.id = value; - return newInstance as ColumnConfigBuilder; + const newInstance = this.clone() as unknown as ColumnConfigBuilder; + newInstance.config.id = value as unknown as TNewId; + return newInstance; } accessor(accessor: TAccessorFn): ColumnConfigBuilder { - const newInstance = this.clone() as any; - newInstance.config.accessor = accessor; - return newInstance as ColumnConfigBuilder; + const newInstance = this.clone() as unknown as ColumnConfigBuilder; + newInstance.config.accessor = accessor as unknown as TAccessorFn; + return newInstance; } displayName(value: string): ColumnConfigBuilder { @@ -50,9 +50,9 @@ class ColumnConfigBuilder< return newInstance; } - icon(value: any): ColumnConfigBuilder { + icon(value: import('lucide-react').LucideIcon): ColumnConfigBuilder { const newInstance = this.clone(); - newInstance.config.icon = value; + newInstance.config.icon = value as unknown as ColumnConfig['icon']; return newInstance; } @@ -60,8 +60,13 @@ class ColumnConfigBuilder< if (this.config.type !== 'number') { throw new Error('min() is only applicable to number columns'); } - const newInstance = this.clone() as any; - newInstance.config.min = value; + const newInstance = this.clone() as unknown as ColumnConfigBuilder< + TData, + TType extends 'number' ? TType : never, + TVal, + TId + >; + newInstance.config.min = value as unknown as ColumnConfig['min']; return newInstance; } @@ -69,8 +74,13 @@ class ColumnConfigBuilder< if (this.config.type !== 'number') { throw new Error('max() is only applicable to number columns'); } - const newInstance = this.clone() as any; - newInstance.config.max = value; + const newInstance = this.clone() as unknown as ColumnConfigBuilder< + TData, + TType extends 'number' ? TType : never, + TVal, + TId + >; + newInstance.config.max = value as unknown as ColumnConfig['max']; return newInstance; } @@ -80,8 +90,13 @@ class ColumnConfigBuilder< if (!isAnyOf(this.config.type, ['option', 'multiOption'])) { throw new Error('options() is only applicable to option or multiOption columns'); } - const newInstance = this.clone() as any; - newInstance.config.options = value; + const newInstance = this.clone() as unknown as ColumnConfigBuilder< + TData, + TType extends 'option' | 'multiOption' ? TType : never, + TVal, + TId + >; + newInstance.config.options = value as unknown as ColumnConfig['options']; return newInstance; } @@ -91,8 +106,13 @@ class ColumnConfigBuilder< if (!isAnyOf(this.config.type, ['option', 'multiOption'])) { throw new Error('transformOptionFn() is only applicable to option or multiOption columns'); } - const newInstance = this.clone() as any; - newInstance.config.transformOptionFn = fn; + const newInstance = this.clone() as unknown as ColumnConfigBuilder< + TData, + TType extends 'option' | 'multiOption' ? TType : never, + TVal, + TId + >; + newInstance.config.transformOptionFn = fn as unknown as ColumnConfig['transformOptionFn']; return newInstance; } @@ -102,8 +122,13 @@ class ColumnConfigBuilder< if (!isAnyOf(this.config.type, ['option', 'multiOption'])) { throw new Error('orderFn() is only applicable to option or multiOption columns'); } - const newInstance = this.clone() as any; - newInstance.config.orderFn = fn; + const newInstance = this.clone() as unknown as ColumnConfigBuilder< + TData, + TType extends 'option' | 'multiOption' ? TType : never, + TVal, + TId + >; + newInstance.config.orderFn = fn as unknown as ColumnConfig['orderFn']; return newInstance; } @@ -159,8 +184,9 @@ export function getColumnOptions( let models = uniq(filtered); if (column.orderFn) { - models = models.sort((m1, m2) => - column.orderFn!(m1 as ElementType>, m2 as ElementType>), + models = models.sort( + (m1, m2) => + column.orderFn?.(m1 as ElementType>, m2 as ElementType>) as number, ); } @@ -168,7 +194,7 @@ export function getColumnOptions( // Memoize transformOptionFn calls const memoizedTransform = memo( () => [models], - (deps) => deps[0].map((m) => column.transformOptionFn!(m as ElementType>)), + (deps) => deps[0].map((m) => column.transformOptionFn?.(m as ElementType>)) as ColumnOption[], { key: `transform-${column.id}` }, ); return memoizedTransform(); @@ -287,17 +313,18 @@ export function getFacetedMinMaxValues( data: TData[], - columnConfigs: ReadonlyArray>, + columnConfigs: ReadonlyArray>, strategy: FilterStrategy, ): Column[] { return columnConfigs.map((columnConfig) => { const getOptions: () => ColumnOption[] = memo( () => [data, strategy, columnConfig.options], - ([data, strategy]) => getColumnOptions(columnConfig, data as any, strategy as any), + ([data, strategy]) => + getColumnOptions(columnConfig as ColumnConfig, data, strategy), { key: `options-${columnConfig.id}` }, ); - const getValues: () => ElementType>[] = memo( + const getValues: () => ElementType>[] = memo( () => [data, strategy], () => (strategy === 'client' ? getColumnValues(columnConfig, data) : []), { key: `values-${columnConfig.id}` }, @@ -305,7 +332,12 @@ export function createColumns( const getUniqueValues: () => Map | undefined = memo( () => [getValues(), strategy], - ([values, strategy]) => getFacetedUniqueValues(columnConfig, values as any, strategy as any), + ([values, strategy]) => + getFacetedUniqueValues( + columnConfig as ColumnConfig, + values as string[] | ColumnOption[], + strategy, + ), { key: `faceted-${columnConfig.id}` }, ); diff --git a/packages/components/src/ui/data-table-filter/core/operators.ts b/packages/components/src/ui/data-table-filter/core/operators.ts index a3ec5645..526b68c1 100644 --- a/packages/components/src/ui/data-table-filter/core/operators.ts +++ b/packages/components/src/ui/data-table-filter/core/operators.ts @@ -1,8 +1,8 @@ import type { ColumnDataType, FilterDetails, - FilterOperatorTarget, FilterOperators, + FilterOperatorTarget, FilterTypeOperatorDetails, FilterValues, } from './types'; diff --git a/packages/components/src/ui/data-table-filter/hooks/use-data-table-filters.tsx b/packages/components/src/ui/data-table-filter/hooks/use-data-table-filters.tsx index 979d0906..66d8a02f 100644 --- a/packages/components/src/ui/data-table-filter/hooks/use-data-table-filters.tsx +++ b/packages/components/src/ui/data-table-filter/hooks/use-data-table-filters.tsx @@ -16,8 +16,7 @@ import type { OptionBasedColumnDataType, OptionColumnIds, } from '../core/types'; -import { uniq } from '../lib/array'; -import { addUniq, removeUniq } from '../lib/array'; +import { addUniq, removeUniq, uniq } from '../lib/array'; import { createDateFilterValue, createNumberFilterValue, diff --git a/packages/components/src/ui/data-table-filter/hooks/use-debounce-callback.tsx b/packages/components/src/ui/data-table-filter/hooks/use-debounce-callback.tsx index d9ab1987..92159e32 100644 --- a/packages/components/src/ui/data-table-filter/hooks/use-debounce-callback.tsx +++ b/packages/components/src/ui/data-table-filter/hooks/use-debounce-callback.tsx @@ -14,12 +14,12 @@ type ControlFunctions = { isPending: () => boolean; }; -export type DebouncedState any> = (( +export type DebouncedState unknown> = (( ...args: Parameters ) => ReturnType | undefined) & ControlFunctions; -export function useDebounceCallback any>( +export function useDebounceCallback unknown>( func: T, delay = 500, options?: DebounceOptions, diff --git a/packages/components/src/ui/data-table-filter/index.tsx b/packages/components/src/ui/data-table-filter/index.tsx index 00663e1a..491c35ce 100644 --- a/packages/components/src/ui/data-table-filter/index.tsx +++ b/packages/components/src/ui/data-table-filter/index.tsx @@ -1,2 +1,2 @@ -export { useDataTableFilters } from './hooks/use-data-table-filters'; export { DataTableFilter } from './components/data-table-filter'; +export { useDataTableFilters } from './hooks/use-data-table-filters'; diff --git a/packages/components/src/ui/data-table-filter/lib/array.ts b/packages/components/src/ui/data-table-filter/lib/array.ts index 50bb7485..af9b218d 100644 --- a/packages/components/src/ui/data-table-filter/lib/array.ts +++ b/packages/components/src/ui/data-table-filter/lib/array.ts @@ -8,24 +8,26 @@ export function intersection(a: T[], b: T[]): T[] { * It uses a cache (WeakMap) to avoid rehashing the same object twice, which is * particularly beneficial if an object appears in multiple places. */ -function deepHash(value: any, cache = new WeakMap()): string { +function deepHash(value: unknown, cache = new WeakMap()): string { // Handle primitives and null/undefined. if (value === null) return 'null'; if (value === undefined) return 'undefined'; const type = typeof value; if (type === 'number' || type === 'boolean' || type === 'string') { - return `${type}:${value.toString()}`; + return `${type}:${String(value)}`; } if (type === 'function') { // Note: using toString for functions. - return `function:${value.toString()}`; + return `function:${String(value)}`; } // For objects and arrays, use caching to avoid repeated work. if (type === 'object') { + const obj = value as object; // If we’ve seen this object before, return the cached hash. - if (cache.has(value)) { - return cache.get(value)!; + const cached = cache.get(obj); + if (cached) { + return cached; } let hash: string; if (Array.isArray(value)) { @@ -33,23 +35,24 @@ function deepHash(value: any, cache = new WeakMap()): string { hash = `array:[${value.map((v) => deepHash(v, cache)).join(',')}]`; } else { // For objects, sort keys to ensure the representation is stable. - const keys = Object.keys(value).sort(); - const props = keys.map((k) => `${k}:${deepHash(value[k], cache)}`).join(','); + const rec = value as Record; + const keys = Object.keys(rec).sort(); + const props = keys.map((k) => `${k}:${deepHash(rec[k], cache)}`).join(','); hash = `object:{${props}}`; } - cache.set(value, hash); + cache.set(obj, hash); return hash; } // Fallback if no case matched. - return `${type}:${value.toString()}`; + return `${type}:${String(value)}`; } /** * Performs deep equality check for any two values. * This recursively checks primitives, arrays, and plain objects. */ -function deepEqual(a: any, b: any): boolean { +function deepEqual(a: unknown, b: unknown): boolean { // Check strict equality first. if (a === b) return true; // If types differ, they’re not equal. @@ -60,7 +63,7 @@ function deepEqual(a: any, b: any): boolean { if (Array.isArray(a)) { if (!Array.isArray(b) || a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { - if (!deepEqual(a[i], b[i])) return false; + if (!deepEqual(a[i], (b as unknown[])[i])) return false; } return true; } @@ -68,12 +71,14 @@ function deepEqual(a: any, b: any): boolean { // Check objects. if (typeof a === 'object') { if (typeof b !== 'object') return false; - const aKeys = Object.keys(a).sort(); - const bKeys = Object.keys(b).sort(); + const aObj = a as Record; + const bObj = b as Record; + const aKeys = Object.keys(aObj).sort(); + const bKeys = Object.keys(bObj).sort(); if (aKeys.length !== bKeys.length) return false; for (let i = 0; i < aKeys.length; i++) { if (aKeys[i] !== bKeys[i]) return false; - if (!deepEqual(a[aKeys[i]], b[bKeys[i]])) return false; + if (!deepEqual(aObj[aKeys[i]], bObj[bKeys[i]])) return false; } return true; } @@ -98,7 +103,7 @@ export function uniq(arr: T[]): T[] { const hash = deepHash(item); if (seen.has(hash)) { // There is a potential duplicate; check the stored items with the same hash. - const itemsWithHash = seen.get(hash)!; + const itemsWithHash = seen.get(hash) as T[]; let duplicateFound = false; for (const existing of itemsWithHash) { if (deepEqual(existing, item)) { diff --git a/packages/components/src/ui/data-table-filter/lib/debounce.ts b/packages/components/src/ui/data-table-filter/lib/debounce.ts index 36b2db06..c0cdcb50 100644 --- a/packages/components/src/ui/data-table-filter/lib/debounce.ts +++ b/packages/components/src/ui/data-table-filter/lib/debounce.ts @@ -10,7 +10,7 @@ type DebounceOptions = { maxWait?: number; }; -export function debounce any>( +export function debounce unknown>( func: T, wait: number, options: DebounceOptions = {}, diff --git a/packages/components/src/ui/data-table-filter/lib/memo.ts b/packages/components/src/ui/data-table-filter/lib/memo.ts index 6e23fce5..b1cf5062 100644 --- a/packages/components/src/ui/data-table-filter/lib/memo.ts +++ b/packages/components/src/ui/data-table-filter/lib/memo.ts @@ -1,7 +1,7 @@ -export function memo( +export function memo( getDeps: () => TDeps, compute: (deps: TDeps) => TResult, - options: { key: string }, + _options: { key: string }, ): () => TResult { let prevDeps: TDeps | undefined; let cachedResult: TResult | undefined; @@ -10,13 +10,12 @@ export function memo( const deps = getDeps(); // If no previous deps or deps have changed, recompute - if (!prevDeps || !shallowEqual(prevDeps, deps)) { + if (!prevDeps || cachedResult === undefined || !shallowEqual(prevDeps, deps)) { cachedResult = compute(deps); prevDeps = deps; - } else { } - return cachedResult!; + return cachedResult as TResult; }; } diff --git a/packages/components/src/ui/data-table/data-table-hooks.ts b/packages/components/src/ui/data-table/data-table-hooks.ts index 9cda0275..dce98f61 100644 --- a/packages/components/src/ui/data-table/data-table-hooks.ts +++ b/packages/components/src/ui/data-table/data-table-hooks.ts @@ -1,7 +1,7 @@ import { type ComponentType, useCallback, useEffect, useMemo } from 'react'; import { useSearchParams } from 'react-router'; import { debounce } from '../utils/debounce'; -import { type DataTableFilterParams, createFilterSchema } from './data-table-schema'; +import { createFilterSchema, type DataTableFilterParams } from './data-table-schema'; /** * Custom hook for managing data table filter state in the URL diff --git a/packages/components/src/ui/data-table/data-table-pagination.tsx b/packages/components/src/ui/data-table/data-table-pagination.tsx index 7c6a42fb..ec273073 100644 --- a/packages/components/src/ui/data-table/data-table-pagination.tsx +++ b/packages/components/src/ui/data-table/data-table-pagination.tsx @@ -23,7 +23,6 @@ export function DataTablePagination({ pageCount, onPaginationChange }: DataTable return (