From a102acf702722d8ff8ccf7970461c5923f278c8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 06:55:42 +0000 Subject: [PATCH 1/3] Initial plan From 8c653ae4703fbfab7ad89a5a02f78a26023017c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 07:03:35 +0000 Subject: [PATCH 2/3] chore: initial plan for swapping out components with @cratis/components Co-authored-by: einari <134365+einari@users.noreply.github.com> --- Library/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Library/package.json b/Library/package.json index 02ebabb..0e0da95 100644 --- a/Library/package.json +++ b/Library/package.json @@ -22,6 +22,7 @@ "@cratis/arc.react": "19.5.8", "@cratis/arc.react.mvvm": "19.5.8", "@cratis/arc.vite": "19.5.8", + "@cratis/components": "^1.0.3", "allotment": "1.20.5", "echarts": "6.0.0", "mobx": "6.15.0", From c25b6ea86485eddb5706b7f2cbeab658b3c063b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 07:07:08 +0000 Subject: [PATCH 3/3] feat: swap out duplicate components with @cratis/components in Library sample Co-authored-by: einari <134365+einari@users.noreply.github.com> --- Library/Components/Common/ErrorBoundary.tsx | 41 ---- Library/Components/Common/FormElement.tsx | 20 -- Library/Components/Common/Page.tsx | 20 -- Library/Components/Common/index.ts | 5 +- Library/Components/DataPage/DataPage.tsx | 191 ------------------ Library/Components/DataPage/index.ts | 3 +- .../DataTableForObservableQuery.tsx | 97 --------- .../DataTables/DataTableForQuery.tsx | 97 --------- Library/Components/DataTables/index.ts | 4 +- .../Dialogs/BusyIndicatorDialog.tsx | 26 --- .../Components/Dialogs/ConfirmationDialog.tsx | 75 ------- Library/Components/Dialogs/index.ts | 4 +- Library/Features/Authors/Authors.tsx | 4 +- .../Authors/Registration/AddAuthor.tsx | 37 +--- Library/Features/Inventory/Adding/AddBook.tsx | 74 ++----- Library/Features/Inventory/Inventory.tsx | 4 +- Library/Layout/Default/DefaultLayout.tsx | 2 +- 17 files changed, 44 insertions(+), 660 deletions(-) delete mode 100644 Library/Components/Common/ErrorBoundary.tsx delete mode 100644 Library/Components/Common/FormElement.tsx delete mode 100644 Library/Components/Common/Page.tsx delete mode 100644 Library/Components/DataPage/DataPage.tsx delete mode 100644 Library/Components/DataTables/DataTableForObservableQuery.tsx delete mode 100644 Library/Components/DataTables/DataTableForQuery.tsx delete mode 100644 Library/Components/Dialogs/BusyIndicatorDialog.tsx delete mode 100644 Library/Components/Dialogs/ConfirmationDialog.tsx diff --git a/Library/Components/Common/ErrorBoundary.tsx b/Library/Components/Common/ErrorBoundary.tsx deleted file mode 100644 index 4b4c9b4..0000000 --- a/Library/Components/Common/ErrorBoundary.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Cratis. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -import { Component, ErrorInfo, ReactNode } from 'react'; - -interface Props { - children: ReactNode; -} -interface State { - hasError: boolean; - error: Error; -} - -export class ErrorBoundary extends Component { - public state: State = { - hasError: false, - error: new Error(), - }; - - public static getDerivedStateFromError(error: Error): State { - return { hasError: true, error: error }; - } - - public componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.error('Uncaught error:', error, errorInfo); - } - - public render() { - if (this.state.hasError) { - return ( -
-

Error

-

{this.state.error.message}

-

{this.state.error.stack}

-
- ); - } - - return this.props.children; - } -} diff --git a/Library/Components/Common/FormElement.tsx b/Library/Components/Common/FormElement.tsx deleted file mode 100644 index 9a02a9a..0000000 --- a/Library/Components/Common/FormElement.tsx +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Cratis. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -export interface FormElementProps { - children: React.ReactNode; - icon: React.ReactNode; -} - -export const FormElement = (props: FormElementProps) => { - return ( -
-
- - {props.icon} - - {props.children} -
-
- ) -} diff --git a/Library/Components/Common/Page.tsx b/Library/Components/Common/Page.tsx deleted file mode 100644 index 84e3eb3..0000000 --- a/Library/Components/Common/Page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Cratis. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -import { HTMLAttributes, ReactNode } from 'react'; - -export interface PageProps extends HTMLAttributes { - title: string; - children?: ReactNode; -} - -export const Page = ({ title, children, ...rest }: PageProps) => { - return ( -
-

{title}

-
- {children} -
-
- ); -}; diff --git a/Library/Components/Common/index.ts b/Library/Components/Common/index.ts index 4e6c08a..3510a18 100644 --- a/Library/Components/Common/index.ts +++ b/Library/Components/Common/index.ts @@ -1,6 +1,5 @@ // Copyright (c) Cratis. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -export * from './ErrorBoundary'; -export * from './Page'; -export * from './FormElement'; +export * from '@cratis/components/Common'; + diff --git a/Library/Components/DataPage/DataPage.tsx b/Library/Components/DataPage/DataPage.tsx deleted file mode 100644 index 1cd7ea0..0000000 --- a/Library/Components/DataPage/DataPage.tsx +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) Cratis. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -import { ReactNode, useMemo } from 'react'; -import { Page } from '../Common/Page'; -import React from 'react'; -import { MenuItem as PrimeMenuItem } from 'primereact/menuitem'; -import { Menubar } from 'primereact/menubar'; -import { IObservableQueryFor, IQueryFor, QueryFor } from '@cratis/arc/queries'; -import { DataTableForObservableQuery } from '../DataTables/DataTableForObservableQuery'; -import { DataTableFilterMeta, DataTableSelectionSingleChangeEvent } from 'primereact/datatable'; -import { DataTableForQuery } from '../DataTables/DataTableForQuery'; -import { Allotment } from 'allotment'; -import { Constructor } from '@cratis/fundamentals'; - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -export interface MenuItemProps extends PrimeMenuItem { - disableOnUnselected?: boolean; -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const MenuItem = (_: MenuItemProps) => { - return null; -}; - -export interface MenuItemsProps { - children: ReactNode; -} - -export interface ColumnProps { - children: ReactNode; -} - -export const MenuItems = ({ children }: MenuItemsProps) => { - const context = React.useContext(DataPageContext); - - const isDisabled = useMemo(() => { - return !context.selectedItem; - }, [context.selectedItem]); - - const items = useMemo(() => { - const menuItems: PrimeMenuItem[] = []; - React.Children.forEach(children, (child) => { - if (React.isValidElement(child) && child.type == MenuItem) { - const Icon = child.props.icon; - const menuItem = { ...child.props }; - menuItem.icon = ; - menuItem.disabled = isDisabled && child.props.disableOnUnselected; - menuItems.push(menuItem); - } - }); - - return menuItems; - }, [children, context.selectedItem]); - - return ( -
- -
); -}; - -export const Columns = ({ children }: ColumnProps) => { - - const context = React.useContext(DataPageContext); - - if (context.query.prototype instanceof QueryFor) { - return ( - - {children} - ); - - } else { - return ( - - {children} - ); - } -}; - -export interface IDetailsComponentProps { - item: TDataType; - -} - -interface IDataPageContext extends DataPageProps { - selectedItem: any; - onSelectionChanged: (e: DataTableSelectionSingleChangeEvent) => void; -} - -const DataPageContext = React.createContext(null as any); - -/** - * Props for the DataPage component - */ -export interface DataPageProps | IObservableQueryFor, TDataType, TArguments> { - /** - * The title of the page - */ - title: string; - - /** - * Children to render, for this it means menu items and columns. Use and for this. - */ - children: ReactNode; - - /** - * Component to render when the selection changes - */ - detailsComponent?: React.FC>; - - /** - * The type of query to use - */ - query: Constructor; - - /** - * Optional arguments to pass to the query - */ - queryArguments?: TArguments; - - /** - * The message to show when there is no data - */ - emptyMessage: string; - - /** - * The key to use for the data - */ - dataKey?: string | undefined; - - /** - * The current selection. - */ - selection?: any[number] | undefined | null; - - /** - * Callback for when the selection changes - */ - onSelectionChange?(event: DataTableSelectionSingleChangeEvent): void; - - /** - * Fields to use for global filtering - */ - globalFilterFields?: string[] | undefined; - - /** - * Default filters to use - */ - defaultFilters?: DataTableFilterMeta; -} - -/** - * Represents a data driven page with a menu and custom defined columns for the data table. - * @param props Props for the DataPage component - * @returns Function to render the DataPage component - */ -const DataPage = | IObservableQueryFor, TDataType, TArguments extends object>(props: DataPageProps) => { - const [selectedItem, setSelectedItem] = React.useState(undefined); - - const selectionChanged = (e: DataTableSelectionSingleChangeEvent) => { - setSelectedItem(e.value); - if (props.onSelectionChange) { - props.onSelectionChange(e); - } - }; - - const context = { ...props, selectedItem, onSelectionChanged: selectionChanged }; - - return ( - - - - - {props.children} - - {props.detailsComponent && selectedItem && - - - - } - - - - ); -}; - -DataPage.MenuItems = MenuItems; -DataPage.Columns = Columns; - -export { DataPage }; diff --git a/Library/Components/DataPage/index.ts b/Library/Components/DataPage/index.ts index a099876..d6f5424 100644 --- a/Library/Components/DataPage/index.ts +++ b/Library/Components/DataPage/index.ts @@ -1,4 +1,5 @@ // Copyright (c) Cratis. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -export * from './DataPage'; +export * from '@cratis/components/DataPage'; + diff --git a/Library/Components/DataTables/DataTableForObservableQuery.tsx b/Library/Components/DataTables/DataTableForObservableQuery.tsx deleted file mode 100644 index 105773f..0000000 --- a/Library/Components/DataTables/DataTableForObservableQuery.tsx +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Cratis. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -import { DataTable, DataTableFilterMeta, DataTableSelectionSingleChangeEvent } from 'primereact/datatable'; -import { Constructor } from '@cratis/fundamentals'; -import { IObservableQueryFor, Paging } from '@cratis/arc/queries'; -import { useObservableQueryWithPaging } from '@cratis/arc.react/queries'; -import { ReactNode, useState } from 'react'; - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -/** - * Props for the DataTableForQuery component - */ -export interface DataTableForObservableQueryProps, TDataType, TArguments> { - /** - * Children to render - */ - children?: ReactNode; - - /** - * The type of query to use - */ - query: Constructor; - - /** - * Optional arguments to pass to the query - */ - queryArguments?: TArguments; - - /** - * The message to show when there is no data - */ - emptyMessage: string; - - /** - * The key to use for the data - */ - dataKey?: string | undefined; - - /** - * The current selection. - */ - selection?: any[number] | undefined | null; - - /** - * Callback for when the selection changes - */ - onSelectionChange?(event: DataTableSelectionSingleChangeEvent): void; - - /** - * Fields to use for global filtering - */ - globalFilterFields?: string[] | undefined; - - /** - * Default filters to use - */ - defaultFilters?: DataTableFilterMeta; -} - -const paging = new Paging(0, 20); - -/** - * Represents a DataTable for a query. - * @param props Props for the component - * @returns Function to render the DataTableForQuery component - */ -export const DataTableForObservableQuery = , TDataType, TArguments extends object>(props: DataTableForObservableQueryProps) => { - const [filters, setFilters] = useState(props.defaultFilters ?? {}); - const [result, , setPage] = useObservableQueryWithPaging(props.query, paging, props.queryArguments); - - return ( - setPage(e.page ?? 0)} - scrollable - scrollHeight={'flex'} - selectionMode='single' - selection={props.selection} - onSelectionChange={props.onSelectionChange} - dataKey={props.dataKey} - filters={filters} - filterDisplay='menu' - onFilter={(e) => setFilters(e.filters)} - globalFilterFields={props.globalFilterFields} - emptyMessage={props.emptyMessage}> - {props.children} - - ); -}; diff --git a/Library/Components/DataTables/DataTableForQuery.tsx b/Library/Components/DataTables/DataTableForQuery.tsx deleted file mode 100644 index 6a5793b..0000000 --- a/Library/Components/DataTables/DataTableForQuery.tsx +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Cratis. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -import { DataTable, DataTableFilterMeta, DataTableSelectionSingleChangeEvent } from 'primereact/datatable'; -import { Constructor } from '@cratis/fundamentals'; -import { IQueryFor, Paging } from '@cratis/arc/queries'; -import { useQueryWithPaging } from '@cratis/arc.react/queries'; -import { ReactNode, useState } from 'react'; - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -/** - * Props for the DataTableForQuery component - */ -export interface DataTableForQueryProps, TDataType, TArguments> { - /** - * Children to render - */ - children?: ReactNode; - - /** - * The type of query to use - */ - query: Constructor; - - /** - * Optional Arguments to pass to the query - */ - queryArguments?: TArguments; - - /** - * The message to show when there is no data - */ - emptyMessage: string; - - /** - * The key to use for the data - */ - dataKey?: string | undefined; - - /** - * The current selection. - */ - selection?: any[number] | undefined | null; - - /** - * Callback for when the selection changes - */ - onSelectionChange?(event: DataTableSelectionSingleChangeEvent): void; - - /** - * Fields to use for global filtering - */ - globalFilterFields?: string[] | undefined; - - /** - * Default filters to use - */ - defaultFilters?: DataTableFilterMeta; -} - -const paging = new Paging(0, 20); - -/** - * Represents a DataTable for a query. - * @param props Props for the component - * @returns Function to render the DataTableForQuery component - */ -export const DataTableForQuery = , TDataType, TArguments extends object>(props: DataTableForQueryProps) => { - const [filters, setFilters] = useState(props.defaultFilters ?? {}); - const [result, , , setPage] = useQueryWithPaging(props.query, paging, props.queryArguments); - - return ( - setPage(e.page ?? 0)} - scrollable - scrollHeight={'flex'} - selectionMode='single' - selection={props.selection} - onSelectionChange={props.onSelectionChange} - dataKey={props.dataKey} - filters={filters} - filterDisplay='menu' - onFilter={(e) => setFilters(e.filters)} - globalFilterFields={props.globalFilterFields} - emptyMessage={props.emptyMessage} > - {props.children} - - ); -}; diff --git a/Library/Components/DataTables/index.ts b/Library/Components/DataTables/index.ts index c9d4a93..34af600 100644 --- a/Library/Components/DataTables/index.ts +++ b/Library/Components/DataTables/index.ts @@ -1,5 +1,5 @@ // Copyright (c) Cratis. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -export * from './DataTableForQuery'; -export * from './DataTableForObservableQuery'; +export * from '@cratis/components/DataTables'; + diff --git a/Library/Components/Dialogs/BusyIndicatorDialog.tsx b/Library/Components/Dialogs/BusyIndicatorDialog.tsx deleted file mode 100644 index ab367e0..0000000 --- a/Library/Components/Dialogs/BusyIndicatorDialog.tsx +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Cratis. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -import { Dialog } from 'primereact/dialog'; -import { BusyIndicatorDialogRequest } from '@cratis/arc.react/dialogs'; -import { ProgressSpinner } from 'primereact/progressspinner'; - -export const BusyIndicatorDialog = (props: BusyIndicatorDialogRequest) => { - - const headerElement = ( -
- {props.title} -
- ); - - return ( - <> - { }}> - -

- {props.message} -

-
- - ); -}; diff --git a/Library/Components/Dialogs/ConfirmationDialog.tsx b/Library/Components/Dialogs/ConfirmationDialog.tsx deleted file mode 100644 index 7d34252..0000000 --- a/Library/Components/Dialogs/ConfirmationDialog.tsx +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Cratis. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -import { Dialog } from 'primereact/dialog'; -import { DialogButtons, ConfirmationDialogRequest } from '@cratis/arc.react/dialogs'; -import { DialogResult, useDialogContext } from '@cratis/arc.react/dialogs'; -import { Button } from 'primereact/button'; - -export const ConfirmationDialog = () => { - const { request, closeDialog } = useDialogContext(); - - const headerElement = ( -
- {request.title} -
- ); - - const okFooter = ( - <> -