@alaarab/ogrid-react-fluent 2.0.9 → 2.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +1 -1
  2. package/dist/esm/ColumnChooser/ColumnChooser.js +10 -128
  3. package/dist/esm/ColumnChooser/ColumnChooser.module.css +45 -10
  4. package/dist/esm/ColumnHeaderFilter/ColumnHeaderFilter.js +16 -36
  5. package/dist/esm/ColumnHeaderFilter/ColumnHeaderFilter.module.css +3 -0
  6. package/dist/esm/ColumnHeaderMenu/ColumnHeaderMenu.js +56 -55
  7. package/dist/esm/ColumnHeaderMenu/ColumnHeaderMenu.module.css +46 -0
  8. package/dist/esm/DataGridTable/DataGridTable.js +121 -292
  9. package/dist/esm/DataGridTable/DataGridTable.module.css +555 -426
  10. package/dist/esm/DataGridTable/DropIndicator.js +5 -0
  11. package/dist/esm/DataGridTable/EmptyState.js +5 -0
  12. package/dist/esm/DataGridTable/GridContextMenu.js +10 -32
  13. package/dist/esm/DataGridTable/LoadingOverlay.js +6 -0
  14. package/dist/esm/{FluentDataTable/FluentDataTable.js → OGrid/OGrid.js} +0 -2
  15. package/dist/esm/OGrid/index.js +1 -0
  16. package/dist/esm/PaginationControls/PaginationControls.js +14 -8
  17. package/dist/esm/index.js +1 -1
  18. package/dist/types/ColumnChooser/ColumnChooser.d.ts +2 -8
  19. package/dist/types/ColumnHeaderFilter/ColumnHeaderFilter.d.ts +2 -20
  20. package/dist/types/ColumnHeaderMenu/ColumnHeaderMenu.d.ts +14 -2
  21. package/dist/types/DataGridTable/DropIndicator.d.ts +7 -0
  22. package/dist/types/DataGridTable/EmptyState.d.ts +11 -0
  23. package/dist/types/DataGridTable/LoadingOverlay.d.ts +6 -0
  24. package/dist/types/{FluentDataTable/FluentDataTable.d.ts → OGrid/OGrid.d.ts} +0 -2
  25. package/dist/types/OGrid/index.d.ts +1 -0
  26. package/dist/types/PaginationControls/PaginationControls.d.ts +2 -10
  27. package/dist/types/index.d.ts +1 -1
  28. package/package.json +5 -5
  29. package/dist/esm/FluentDataTable/index.js +0 -1
  30. package/dist/types/FluentDataTable/index.d.ts +0 -1
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import styles from './DataGridTable.module.css';
3
+ export function DropIndicator({ dropIndicatorX, wrapperLeft }) {
4
+ return (_jsx("div", { className: styles.dropIndicator, style: { left: dropIndicatorX - wrapperLeft } }));
5
+ }
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import styles from './DataGridTable.module.css';
3
+ export function EmptyState({ emptyState }) {
4
+ return (_jsx("div", { className: styles.emptyStateInGrid, children: _jsx("div", { className: styles.emptyStateInGridMessageSticky, children: emptyState.render ? (emptyState.render()) : (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.emptyStateInGridIcon, "aria-hidden": true, children: '\uD83D\uDCCB' }), _jsx("div", { className: styles.emptyStateInGridTitle, children: "No results found" }), _jsx("div", { className: styles.emptyStateInGridMessage, children: emptyState.message != null ? (emptyState.message) : emptyState.hasActiveFilters ? (_jsxs(_Fragment, { children: ["No items match your current filters. Try adjusting your search or", ' ', _jsx("button", { type: "button", className: styles.emptyStateInGridLink, onClick: emptyState.onClearAll, children: "clear all filters" }), ' ', "to see all items."] })) : ('There are no items available at this time.') })] })) }) }));
5
+ }
@@ -1,35 +1,13 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import * as React from 'react';
3
- import { GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut } from '@alaarab/ogrid-react';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { GridContextMenu as BaseGridContextMenu } from '@alaarab/ogrid-react';
4
3
  import styles from './DataGridTable.module.css';
4
+ const classNames = {
5
+ contextMenu: styles.contextMenu,
6
+ contextMenuItem: styles.contextMenuItem,
7
+ contextMenuItemLabel: styles.contextMenuItemLabel,
8
+ contextMenuItemShortcut: styles.contextMenuItemShortcut,
9
+ contextMenuDivider: styles.contextMenuDivider,
10
+ };
5
11
  export function GridContextMenu(props) {
6
- const { x, y, hasSelection, canUndo, canRedo, onClose } = props;
7
- const ref = React.useRef(null);
8
- const handlers = React.useMemo(() => getContextMenuHandlers(props), [props]);
9
- const isDisabled = React.useCallback((item) => {
10
- if (item.disabledWhenNoSelection && !hasSelection)
11
- return true;
12
- if (item.id === 'undo' && !canUndo)
13
- return true;
14
- if (item.id === 'redo' && !canRedo)
15
- return true;
16
- return false;
17
- }, [hasSelection, canUndo, canRedo]);
18
- React.useEffect(() => {
19
- const handleClickOutside = (e) => {
20
- if (ref.current && !ref.current.contains(e.target))
21
- onClose();
22
- };
23
- const handleKeyDown = (e) => {
24
- if (e.key === 'Escape')
25
- onClose();
26
- };
27
- document.addEventListener('mousedown', handleClickOutside, true);
28
- document.addEventListener('keydown', handleKeyDown, true);
29
- return () => {
30
- document.removeEventListener('mousedown', handleClickOutside, true);
31
- document.removeEventListener('keydown', handleKeyDown, true);
32
- };
33
- }, [onClose]);
34
- return (_jsx("div", { ref: ref, className: styles.contextMenu, role: "menu", style: { left: x, top: y }, "aria-label": "Grid context menu", children: GRID_CONTEXT_MENU_ITEMS.map((item) => (_jsxs(React.Fragment, { children: [item.dividerBefore && _jsx("div", { className: styles.contextMenuDivider }), _jsxs("button", { type: "button", className: styles.contextMenuItem, onClick: handlers[item.id], disabled: isDisabled(item), children: [_jsx("span", { className: styles.contextMenuItemLabel, children: item.label }), item.shortcut && (_jsx("span", { className: styles.contextMenuItemShortcut, children: formatShortcut(item.shortcut) }))] })] }, item.id))) }));
12
+ return _jsx(BaseGridContextMenu, { ...props, classNames: classNames });
35
13
  }
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Spinner } from '@fluentui/react-components';
3
+ import styles from './DataGridTable.module.css';
4
+ export function LoadingOverlay({ message }) {
5
+ return (_jsx("div", { className: styles.loadingOverlay, "aria-live": "polite", children: _jsxs("div", { className: styles.loadingOverlayContent, children: [_jsx(Spinner, { size: "small" }), _jsx("span", { className: styles.loadingOverlayText, children: message })] }) }));
6
+ }
@@ -14,5 +14,3 @@ const OGridInner = forwardRef(function OGridInner(props, ref) {
14
14
  });
15
15
  OGridInner.displayName = 'OGrid';
16
16
  export const OGrid = React.memo(OGridInner);
17
- /** @deprecated Use `OGrid` instead. Backward-compat alias. */
18
- export const FluentDataTable = OGrid;
@@ -0,0 +1 @@
1
+ export { OGrid } from './OGrid';
@@ -1,20 +1,26 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
- import { useMemo, useCallback } from 'react';
4
3
  import { Button, Select } from '@fluentui/react-components';
5
4
  import { ChevronLeftRegular, ChevronRightRegular, ChevronDoubleLeftRegular, ChevronDoubleRightRegular, } from '@fluentui/react-icons';
6
- import { getPaginationViewModel } from '@alaarab/ogrid-react';
5
+ import { usePaginationControls } from '@alaarab/ogrid-react';
7
6
  import styles from './PaginationControls.module.css';
8
7
  export const PaginationControls = React.memo((props) => {
9
8
  const { currentPage, pageSize, totalCount, onPageChange, onPageSizeChange, pageSizeOptions, entityLabelPlural, className } = props;
10
- const labelPlural = entityLabelPlural ?? 'items';
11
- const vm = useMemo(() => getPaginationViewModel(currentPage, pageSize, totalCount, pageSizeOptions ? { pageSizeOptions } : undefined), [currentPage, pageSize, totalCount, pageSizeOptions]);
12
- const handlePageSizeChange = useCallback((_e, data) => {
13
- onPageSizeChange(Number(data.value));
14
- }, [onPageSizeChange]);
9
+ const { labelPlural, vm, handlePageSizeChange } = usePaginationControls({
10
+ currentPage,
11
+ pageSize,
12
+ totalCount,
13
+ onPageChange,
14
+ onPageSizeChange,
15
+ pageSizeOptions,
16
+ entityLabelPlural,
17
+ });
18
+ const handlePageSizeChangeEvent = (_e, data) => {
19
+ handlePageSizeChange(Number(data.value));
20
+ };
15
21
  if (!vm) {
16
22
  return null;
17
23
  }
18
24
  const { pageNumbers, showStartEllipsis, showEndEllipsis, totalPages, startItem, endItem } = vm;
19
- return (_jsxs("div", { className: `${styles.pagination} ${className || ''}`, role: "navigation", "aria-label": "Pagination", children: [_jsxs("div", { className: styles.paginationInfo, children: ["Showing ", startItem, " to ", endItem, " of ", totalCount.toLocaleString(), " ", labelPlural] }), _jsxs("div", { className: styles.paginationControls, children: [_jsx(Button, { appearance: "outline", shape: "circular", size: "small", icon: _jsx(ChevronDoubleLeftRegular, {}), onClick: () => onPageChange(1), disabled: currentPage === 1, "aria-label": "First page", className: styles.navBtn }), _jsx(Button, { appearance: "outline", shape: "circular", size: "small", icon: _jsx(ChevronLeftRegular, {}), onClick: () => onPageChange(currentPage - 1), disabled: currentPage === 1, "aria-label": "Previous page", className: styles.navBtn }), _jsxs("div", { className: styles.pageNumbers, children: [showStartEllipsis && (_jsxs(_Fragment, { children: [_jsx(Button, { appearance: "outline", size: "small", shape: "rounded", onClick: () => onPageChange(1), "aria-label": "Page 1", className: styles.pageBtn, children: "1" }), _jsx("span", { className: styles.ellipsis, "aria-hidden": true, children: "\u2026" })] })), pageNumbers.map((pageNum) => (_jsx(Button, { appearance: currentPage === pageNum ? 'primary' : 'outline', size: "small", shape: "rounded", onClick: () => onPageChange(pageNum), "aria-label": `Page ${pageNum}`, "aria-current": currentPage === pageNum ? 'page' : undefined, className: styles.pageBtn, children: pageNum }, pageNum))), showEndEllipsis && (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.ellipsis, "aria-hidden": true, children: "\u2026" }), _jsx(Button, { appearance: "outline", size: "small", shape: "rounded", onClick: () => onPageChange(totalPages), "aria-label": `Page ${totalPages}`, className: styles.pageBtn, children: totalPages })] }))] }), _jsx(Button, { appearance: "outline", shape: "circular", size: "small", icon: _jsx(ChevronRightRegular, {}), onClick: () => onPageChange(currentPage + 1), disabled: currentPage >= totalPages, "aria-label": "Next page", className: styles.navBtn }), _jsx(Button, { appearance: "outline", shape: "circular", size: "small", icon: _jsx(ChevronDoubleRightRegular, {}), onClick: () => onPageChange(totalPages), disabled: currentPage >= totalPages, "aria-label": "Last page", className: styles.navBtn })] }), _jsxs("div", { className: styles.pageSizeSelector, children: [_jsx("span", { className: styles.pageSizeLabel, children: "Rows" }), _jsx(Select, { value: String(pageSize), onChange: handlePageSizeChange, size: "small", appearance: "outline", "aria-label": "Rows per page", className: styles.pageSizeSelect, children: vm.pageSizeOptions.map((n) => (_jsx("option", { value: n, children: n }, n))) })] })] }));
25
+ return (_jsxs("div", { className: `${styles.pagination} ${className || ''}`, role: "navigation", "aria-label": "Pagination", children: [_jsxs("div", { className: styles.paginationInfo, children: ["Showing ", startItem, " to ", endItem, " of ", totalCount.toLocaleString(), " ", labelPlural] }), _jsxs("div", { className: styles.paginationControls, children: [_jsx(Button, { appearance: "outline", shape: "circular", size: "small", icon: _jsx(ChevronDoubleLeftRegular, {}), onClick: () => onPageChange(1), disabled: currentPage === 1, "aria-label": "First page", className: styles.navBtn }), _jsx(Button, { appearance: "outline", shape: "circular", size: "small", icon: _jsx(ChevronLeftRegular, {}), onClick: () => onPageChange(currentPage - 1), disabled: currentPage === 1, "aria-label": "Previous page", className: styles.navBtn }), _jsxs("div", { className: styles.pageNumbers, children: [showStartEllipsis && (_jsxs(_Fragment, { children: [_jsx(Button, { appearance: "outline", size: "small", shape: "rounded", onClick: () => onPageChange(1), "aria-label": "Page 1", className: styles.pageBtn, children: "1" }), _jsx("span", { className: styles.ellipsis, "aria-hidden": true, children: "\u2026" })] })), pageNumbers.map((pageNum) => (_jsx(Button, { appearance: currentPage === pageNum ? 'primary' : 'outline', size: "small", shape: "rounded", onClick: () => onPageChange(pageNum), "aria-label": `Page ${pageNum}`, "aria-current": currentPage === pageNum ? 'page' : undefined, className: styles.pageBtn, children: pageNum }, pageNum))), showEndEllipsis && (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.ellipsis, "aria-hidden": true, children: "\u2026" }), _jsx(Button, { appearance: "outline", size: "small", shape: "rounded", onClick: () => onPageChange(totalPages), "aria-label": `Page ${totalPages}`, className: styles.pageBtn, children: totalPages })] }))] }), _jsx(Button, { appearance: "outline", shape: "circular", size: "small", icon: _jsx(ChevronRightRegular, {}), onClick: () => onPageChange(currentPage + 1), disabled: currentPage >= totalPages, "aria-label": "Next page", className: styles.navBtn }), _jsx(Button, { appearance: "outline", shape: "circular", size: "small", icon: _jsx(ChevronDoubleRightRegular, {}), onClick: () => onPageChange(totalPages), disabled: currentPage >= totalPages, "aria-label": "Last page", className: styles.navBtn })] }), _jsxs("div", { className: styles.pageSizeSelector, children: [_jsx("span", { className: styles.pageSizeLabel, children: "Rows" }), _jsx(Select, { value: String(pageSize), onChange: handlePageSizeChangeEvent, size: "small", appearance: "outline", "aria-label": "Rows per page", className: styles.pageSizeSelect, children: vm.pageSizeOptions.map((n) => (_jsx("option", { value: n, children: n }, n))) })] })] }));
20
26
  });
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Components
2
- export { OGrid, FluentDataTable } from './FluentDataTable';
2
+ export { OGrid } from './OGrid';
3
3
  export { DataGridTable } from './DataGridTable/DataGridTable';
4
4
  export { ColumnChooser } from './ColumnChooser/ColumnChooser';
5
5
  export { ColumnHeaderFilter } from './ColumnHeaderFilter/ColumnHeaderFilter';
@@ -1,10 +1,4 @@
1
1
  import * as React from 'react';
2
- import type { IColumnDefinition } from '@alaarab/ogrid-react';
3
- export type { IColumnDefinition };
4
- export interface IColumnChooserProps {
5
- columns: IColumnDefinition[];
6
- visibleColumns: Set<string>;
7
- onVisibilityChange: (columnKey: string, visible: boolean) => void;
8
- className?: string;
9
- }
2
+ import type { IColumnChooserProps } from '@alaarab/ogrid-react';
3
+ export type { IColumnChooserProps };
10
4
  export declare const ColumnChooser: React.FC<IColumnChooserProps>;
@@ -1,22 +1,4 @@
1
1
  import * as React from 'react';
2
- import type { UserLike, ColumnFilterType, IDateFilterValue } from '@alaarab/ogrid-react';
3
- export interface IColumnHeaderFilterProps {
4
- columnKey: string;
5
- columnName: string;
6
- filterType: ColumnFilterType;
7
- isSorted?: boolean;
8
- isSortedDescending?: boolean;
9
- onSort?: () => void;
10
- selectedValues?: string[];
11
- onFilterChange?: (values: string[]) => void;
12
- options?: string[];
13
- isLoadingOptions?: boolean;
14
- textValue?: string;
15
- onTextChange?: (value: string) => void;
16
- selectedUser?: UserLike;
17
- onUserChange?: (user: UserLike | undefined) => void;
18
- peopleSearch?: (query: string) => Promise<UserLike[]>;
19
- dateValue?: IDateFilterValue;
20
- onDateChange?: (value: IDateFilterValue | undefined) => void;
21
- }
2
+ import type { IColumnHeaderFilterProps } from '@alaarab/ogrid-react';
3
+ export type { IColumnHeaderFilterProps };
22
4
  export declare const ColumnHeaderFilter: React.FC<IColumnHeaderFilterProps>;
@@ -1,4 +1,4 @@
1
- import * as React from 'react';
1
+ import React from 'react';
2
2
  export interface ColumnHeaderMenuProps {
3
3
  isOpen: boolean;
4
4
  anchorElement: HTMLElement | null;
@@ -6,8 +6,20 @@ export interface ColumnHeaderMenuProps {
6
6
  onPinLeft: () => void;
7
7
  onPinRight: () => void;
8
8
  onUnpin: () => void;
9
+ onSortAsc: () => void;
10
+ onSortDesc: () => void;
11
+ onClearSort: () => void;
12
+ onAutosizeThis: () => void;
13
+ onAutosizeAll: () => void;
9
14
  canPinLeft: boolean;
10
15
  canPinRight: boolean;
11
16
  canUnpin: boolean;
17
+ currentSort: 'asc' | 'desc' | null;
18
+ isSortable: boolean;
19
+ isResizable: boolean;
12
20
  }
13
- export declare function ColumnHeaderMenu(props: ColumnHeaderMenuProps): React.ReactElement | null;
21
+ /**
22
+ * Column header dropdown menu for pin/sort/autosize actions.
23
+ * Uses positioned div with portal rendering.
24
+ */
25
+ export declare function ColumnHeaderMenu(props: ColumnHeaderMenuProps): React.ReactPortal | null;
@@ -0,0 +1,7 @@
1
+ import * as React from 'react';
2
+ interface DropIndicatorProps {
3
+ dropIndicatorX: number;
4
+ wrapperLeft: number;
5
+ }
6
+ export declare function DropIndicator({ dropIndicatorX, wrapperLeft }: DropIndicatorProps): React.ReactElement;
7
+ export {};
@@ -0,0 +1,11 @@
1
+ import * as React from 'react';
2
+ interface EmptyStateProps {
3
+ emptyState: {
4
+ render?: () => React.ReactNode;
5
+ message?: React.ReactNode;
6
+ hasActiveFilters?: boolean;
7
+ onClearAll?: () => void;
8
+ };
9
+ }
10
+ export declare function EmptyState({ emptyState }: EmptyStateProps): React.ReactElement;
11
+ export {};
@@ -0,0 +1,6 @@
1
+ import * as React from 'react';
2
+ interface LoadingOverlayProps {
3
+ message: string;
4
+ }
5
+ export declare function LoadingOverlay({ message }: LoadingOverlayProps): React.ReactElement;
6
+ export {};
@@ -3,5 +3,3 @@ import { type IOGridProps, type IOGridApi } from '@alaarab/ogrid-react';
3
3
  export type { IOGridProps } from '@alaarab/ogrid-react';
4
4
  declare const OGridInner: React.ForwardRefExoticComponent<IOGridProps<unknown> & React.RefAttributes<IOGridApi<unknown>>>;
5
5
  export declare const OGrid: typeof OGridInner;
6
- /** @deprecated Use `OGrid` instead. Backward-compat alias. */
7
- export declare const FluentDataTable: React.ForwardRefExoticComponent<IOGridProps<unknown> & React.RefAttributes<IOGridApi<unknown>>>;
@@ -0,0 +1 @@
1
+ export { OGrid, type IOGridProps } from './OGrid';
@@ -1,12 +1,4 @@
1
1
  import * as React from 'react';
2
- export interface IPaginationControlsProps {
3
- currentPage: number;
4
- pageSize: number;
5
- totalCount: number;
6
- onPageChange: (page: number) => void;
7
- onPageSizeChange: (pageSize: number) => void;
8
- pageSizeOptions?: number[];
9
- entityLabelPlural?: string;
10
- className?: string;
11
- }
2
+ import type { IPaginationControlsProps } from '@alaarab/ogrid-react';
3
+ export type { IPaginationControlsProps };
12
4
  export declare const PaginationControls: React.FC<IPaginationControlsProps>;
@@ -1,4 +1,4 @@
1
- export { OGrid, FluentDataTable, type IOGridProps } from './FluentDataTable';
1
+ export { OGrid, type IOGridProps } from './OGrid';
2
2
  export { DataGridTable } from './DataGridTable/DataGridTable';
3
3
  export { ColumnChooser, type IColumnChooserProps } from './ColumnChooser/ColumnChooser';
4
4
  export { ColumnHeaderFilter, type IColumnHeaderFilterProps } from './ColumnHeaderFilter/ColumnHeaderFilter';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-react-fluent",
3
- "version": "2.0.9",
4
- "description": "OGrid Fluent UI implementation – DataGrid-powered data table with sorting, filtering, pagination, column chooser, and CSV export.",
3
+ "version": "2.0.12",
4
+ "description": "OGrid React Fluent implementation – DataGrid-powered data table with sorting, filtering, pagination, column chooser, and CSV export.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
7
7
  "types": "dist/types/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "node": ">=18"
41
41
  },
42
42
  "dependencies": {
43
- "@alaarab/ogrid-react": "2.0.9"
43
+ "@alaarab/ogrid-react": "2.0.12"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@fluentui/react-components": "^9.0.0",
@@ -55,8 +55,8 @@
55
55
  "@testing-library/jest-dom": "^6.9.1",
56
56
  "@testing-library/react": "^16.3.2",
57
57
  "@testing-library/user-event": "^14.6.1",
58
- "@types/react": "^18.3.18",
59
- "@types/react-dom": "^18.3.5",
58
+ "@types/react": "^19.0.0",
59
+ "@types/react-dom": "^19.0.0",
60
60
  "eslint-plugin-storybook": "10.2.8",
61
61
  "react": "^18.3.1",
62
62
  "react-dom": "^18.3.1",
@@ -1 +0,0 @@
1
- export { OGrid, FluentDataTable } from './FluentDataTable';
@@ -1 +0,0 @@
1
- export { OGrid, FluentDataTable, type IOGridProps } from './FluentDataTable';