A lot of refectoring
This commit is contained in:
parent
4186219c50
commit
c92cabc569
42
package.json
42
package.json
@ -48,9 +48,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@glideapps/glide-data-grid": "^6.0.3",
|
"@glideapps/glide-data-grid": "^6.0.3",
|
||||||
"@mantine/notifications": "^8.3.1",
|
"@mantine/notifications": "^8.3.5",
|
||||||
"@tabler/icons-react": "^3.35.0",
|
"@tabler/icons-react": "^3.35.0",
|
||||||
"@tanstack/react-query": "^5.89.0",
|
"@tanstack/react-query": "^5.90.5",
|
||||||
"immer": "^10.1.3",
|
"immer": "^10.1.3",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
@ -59,32 +59,33 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.29.7",
|
"@changesets/cli": "^2.29.7",
|
||||||
"@eslint/js": "^9.35.0",
|
"@eslint/js": "^9.38.0",
|
||||||
"@storybook/react-vite": "^9.1.7",
|
"@storybook/react-vite": "^9.1.13",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/node": "^24.4.0",
|
"@types/node": "^24.9.1",
|
||||||
"@types/react": "^19.1.13",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.2.2",
|
||||||
"@typescript-eslint/parser": "^8.43.0",
|
"@typescript-eslint/parser": "^8.46.2",
|
||||||
"@vitejs/plugin-react-swc": "^4.0.1",
|
"@vitejs/plugin-react-swc": "^4.1.0",
|
||||||
"eslint": "^9.35.0",
|
"eslint": "^9.38.0",
|
||||||
"eslint-config-mantine": "^4.0.3",
|
"eslint-config-mantine": "^4.0.3",
|
||||||
"eslint-plugin-perfectionist": "^4.15.0",
|
"eslint-plugin-perfectionist": "^4.15.1",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^7.0.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
"eslint-plugin-storybook": "^9.1.7",
|
"eslint-plugin-storybook": "^9.1.13",
|
||||||
"global": "^4.4.0",
|
"global": "^4.4.0",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
"jsdom": "^27.0.0",
|
"jsdom": "^27.0.1",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"prettier-eslint": "^16.4.2",
|
"prettier-eslint": "^16.4.2",
|
||||||
"react-dom": "^19.1.1",
|
"react": "^19.2.0",
|
||||||
"storybook": "^9.1.7",
|
"react-dom": "^19.2.0",
|
||||||
"typescript": "~5.9.2",
|
"storybook": "^9.1.13",
|
||||||
"typescript-eslint": "^8.43.0",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.1.5",
|
"typescript-eslint": "^8.46.2",
|
||||||
|
"vite": "^7.1.11",
|
||||||
"vite-plugin-dts": "^4.5.4",
|
"vite-plugin-dts": "^4.5.4",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
@ -95,6 +96,7 @@
|
|||||||
"@warkypublic/artemis-kit": "^1.0.10",
|
"@warkypublic/artemis-kit": "^1.0.10",
|
||||||
"@warkypublic/zustandsyncstore": "^0.0.4",
|
"@warkypublic/zustandsyncstore": "^0.0.4",
|
||||||
"react": ">= 19.0.0",
|
"react": ">= 19.0.0",
|
||||||
|
"react-dom": ">= 19.0.0",
|
||||||
"use-sync-external-store": ">= 1.4.0",
|
"use-sync-external-store": ">= 1.4.0",
|
||||||
"zustand": ">= 5.0.0"
|
"zustand": ">= 5.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
988
pnpm-lock.yaml
988
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,71 +1,73 @@
|
|||||||
import { MantineProvider } from '@mantine/core'
|
import { MantineProvider } from '@mantine/core';
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react';
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { Gridler } from './Gridler'
|
import { Gridler } from './Gridler';
|
||||||
|
|
||||||
// Mock the complex sub-components
|
// Mock the complex sub-components
|
||||||
vi.mock('./GridlerDataGrid', () => ({
|
vi.mock('./GridlerDataGrid', () => ({
|
||||||
GridlerDataGrid: () => <div data-testid="gridler-data-grid">Data Grid</div>
|
GridlerDataGrid: () => <div data-testid="gridler-data-grid">Data Grid</div>,
|
||||||
}))
|
}));
|
||||||
|
|
||||||
vi.mock('../MantineBetterMenu', () => ({
|
vi.mock('../MantineBetterMenu', () => ({
|
||||||
MantineBetterMenusProvider: ({ children }: { children: React.ReactNode }) => (
|
MantineBetterMenusProvider: ({ children }: { children: React.ReactNode }) => (
|
||||||
<div data-testid="menu-provider">{children}</div>
|
<div data-testid="menu-provider">{children}</div>
|
||||||
)
|
),
|
||||||
}))
|
useMantineBetterMenus: () => ({
|
||||||
|
hide: vi.fn(),
|
||||||
|
show: vi.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
// Mock the Store Provider
|
// Mock the Store Provider
|
||||||
vi.mock('./components/Store', () => ({
|
vi.mock('./components/GridlerStore', () => ({
|
||||||
Provider: ({ children, uniqueid }: { children: React.ReactNode; uniqueid: string }) => (
|
Provider: ({ children, uniqueid }: { children: React.ReactNode; uniqueid: string }) => (
|
||||||
<div data-testid={`store-provider-${uniqueid}`}>{children}</div>
|
<div data-testid={`store-provider-${uniqueid}`}>{children}</div>
|
||||||
)
|
),
|
||||||
}))
|
}));
|
||||||
|
|
||||||
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||||
<MantineProvider>
|
<MantineProvider>{children}</MantineProvider>
|
||||||
{children}
|
);
|
||||||
</MantineProvider>
|
|
||||||
)
|
|
||||||
|
|
||||||
describe('Gridler', () => {
|
describe('Gridler', () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
cols: [
|
cols: [
|
||||||
{ key: 'id', title: 'ID' },
|
{ key: 'id', title: 'ID' },
|
||||||
{ key: 'name', title: 'Name' }
|
{ key: 'name', title: 'Name' },
|
||||||
],
|
],
|
||||||
uniqueid: 'test-grid'
|
uniqueid: 'test-grid',
|
||||||
}
|
};
|
||||||
|
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
render(
|
render(
|
||||||
<TestWrapper>
|
<TestWrapper>
|
||||||
<Gridler {...defaultProps} />
|
<Gridler {...defaultProps} />
|
||||||
</TestWrapper>
|
</TestWrapper>
|
||||||
)
|
);
|
||||||
|
|
||||||
expect(screen.getByTestId('gridler-data-grid')).toBeInTheDocument()
|
expect(screen.getByTestId('gridler-data-grid')).toBeInTheDocument();
|
||||||
})
|
});
|
||||||
|
|
||||||
it('wraps content with MantineBetterMenusProvider', () => {
|
it('wraps content with MantineBetterMenusProvider', () => {
|
||||||
render(
|
render(
|
||||||
<TestWrapper>
|
<TestWrapper>
|
||||||
<Gridler {...defaultProps} />
|
<Gridler {...defaultProps} />
|
||||||
</TestWrapper>
|
</TestWrapper>
|
||||||
)
|
);
|
||||||
|
|
||||||
expect(screen.getByTestId('menu-provider')).toBeInTheDocument()
|
expect(screen.getByTestId('menu-provider')).toBeInTheDocument();
|
||||||
})
|
});
|
||||||
|
|
||||||
it('creates store provider with unique ID', () => {
|
it('creates store provider with unique ID', () => {
|
||||||
render(
|
render(
|
||||||
<TestWrapper>
|
<TestWrapper>
|
||||||
<Gridler {...defaultProps} />
|
<Gridler {...defaultProps} />
|
||||||
</TestWrapper>
|
</TestWrapper>
|
||||||
)
|
);
|
||||||
|
|
||||||
expect(screen.getByTestId('store-provider-test-grid')).toBeInTheDocument()
|
expect(screen.getByTestId('store-provider-test-grid')).toBeInTheDocument();
|
||||||
})
|
});
|
||||||
|
|
||||||
it('renders children when provided', () => {
|
it('renders children when provided', () => {
|
||||||
render(
|
render(
|
||||||
@ -74,18 +76,18 @@ describe('Gridler', () => {
|
|||||||
<div data-testid="test-child">Custom Child</div>
|
<div data-testid="test-child">Custom Child</div>
|
||||||
</Gridler>
|
</Gridler>
|
||||||
</TestWrapper>
|
</TestWrapper>
|
||||||
)
|
);
|
||||||
|
|
||||||
expect(screen.getByTestId('test-child')).toBeInTheDocument()
|
expect(screen.getByTestId('test-child')).toBeInTheDocument();
|
||||||
})
|
});
|
||||||
|
|
||||||
it('handles different uniqueid prop', () => {
|
it('handles different uniqueid prop', () => {
|
||||||
render(
|
render(
|
||||||
<TestWrapper>
|
<TestWrapper>
|
||||||
<Gridler {...defaultProps} uniqueid="different-grid" />
|
<Gridler {...defaultProps} uniqueid="different-grid" />
|
||||||
</TestWrapper>
|
</TestWrapper>
|
||||||
)
|
);
|
||||||
|
|
||||||
expect(screen.getByTestId('store-provider-different-grid')).toBeInTheDocument()
|
expect(screen.getByTestId('store-provider-different-grid')).toBeInTheDocument();
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@ -2,13 +2,12 @@ import '@glideapps/glide-data-grid/dist/index.css';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { MantineBetterMenusProvider } from '../MantineBetterMenu';
|
import { MantineBetterMenusProvider } from '../MantineBetterMenu';
|
||||||
import { APIAdaptorGoLangv2 } from './components/APIAdaptorGoLangv2';
|
import { GlidlerFormAdaptor } from './components/adaptors/GlidlerFormAdaptor';
|
||||||
import { GlidlerFormInterface } from './components/GridlerFormInterface';
|
import { GlidlerLocalDataAdaptor } from './components/adaptors/GlidlerLocalDataAdaptor';
|
||||||
import { LocalDataAdaptor } from './components/LocalDataAdaptor';
|
import { type GridlerProps, Provider } from './components/GridlerStore';
|
||||||
import { type GridlerProps, Provider } from './components/Store';
|
|
||||||
import { GridlerDataGrid } from './GridlerDataGrid';
|
import { GridlerDataGrid } from './GridlerDataGrid';
|
||||||
|
|
||||||
export const Gridler = (props: GridlerProps) => {
|
const Gridler = (props: GridlerProps) => {
|
||||||
return (
|
return (
|
||||||
<MantineBetterMenusProvider>
|
<MantineBetterMenusProvider>
|
||||||
<Provider
|
<Provider
|
||||||
@ -27,6 +26,8 @@ export const Gridler = (props: GridlerProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Gridler.GlidlerFormInterface = GlidlerFormInterface;
|
Gridler.FormAdaptor = GlidlerFormAdaptor;
|
||||||
Gridler.APIAdaptorGoLangv2 = APIAdaptorGoLangv2;
|
Gridler.LocalDataAdaptor = GlidlerLocalDataAdaptor;
|
||||||
Gridler.LocalDataAdaptor = LocalDataAdaptor;
|
|
||||||
|
export { Gridler };
|
||||||
|
export default Gridler;
|
||||||
|
|||||||
@ -5,21 +5,20 @@ import {
|
|||||||
type DataEditorRef,
|
type DataEditorRef,
|
||||||
type GridColumn,
|
type GridColumn,
|
||||||
} from '@glideapps/glide-data-grid';
|
} from '@glideapps/glide-data-grid';
|
||||||
import { ActionIcon, Group, Stack } from '@mantine/core';
|
import { Group, Stack } from '@mantine/core';
|
||||||
import { useElementSize, useMergedRef } from '@mantine/hooks';
|
import { useMergedRef } from '@mantine/hooks';
|
||||||
import { IconMenu2 } from '@tabler/icons-react';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { BottomBar } from './components/BottomBar';
|
import { BottomBar } from './components/BottomBar';
|
||||||
import { Computer } from './components/Computer';
|
import { Computer } from './components/Computer';
|
||||||
|
import { useGridlerStore } from './components/GridlerStore';
|
||||||
import { Pager } from './components/Pager';
|
import { Pager } from './components/Pager';
|
||||||
|
import { RightMenuIcon } from './components/RightMenuIcon';
|
||||||
import { SortSprite } from './components/sprites/Sort';
|
import { SortSprite } from './components/sprites/Sort';
|
||||||
import { SortDownSprite } from './components/sprites/SortDown';
|
import { SortDownSprite } from './components/sprites/SortDown';
|
||||||
import { SortUpSprite } from './components/sprites/SortUp';
|
import { SortUpSprite } from './components/sprites/SortUp';
|
||||||
import { useGridlerStore } from './components/Store';
|
|
||||||
import classes from './Gridler.module.css';
|
import classes from './Gridler.module.css';
|
||||||
import { useGridTheme } from './hooks/use-grid-theme';
|
import { useGridTheme } from './hooks/use-grid-theme';
|
||||||
import { RightMenuIcon } from './components/RightMenuIcon';
|
|
||||||
|
|
||||||
export const GridlerDataGrid = () => {
|
export const GridlerDataGrid = () => {
|
||||||
const ref = React.useRef<DataEditorRef | null>(null);
|
const ref = React.useRef<DataEditorRef | null>(null);
|
||||||
@ -34,6 +33,7 @@ export const GridlerDataGrid = () => {
|
|||||||
glideProps,
|
glideProps,
|
||||||
hasLocalData,
|
hasLocalData,
|
||||||
headerHeight,
|
headerHeight,
|
||||||
|
heightProp,
|
||||||
mounted,
|
mounted,
|
||||||
onCellEdited,
|
onCellEdited,
|
||||||
onColumnMoved,
|
onColumnMoved,
|
||||||
@ -51,7 +51,6 @@ export const GridlerDataGrid = () => {
|
|||||||
setState,
|
setState,
|
||||||
setStateFN,
|
setStateFN,
|
||||||
total_rows,
|
total_rows,
|
||||||
heightProp,
|
|
||||||
widthProp,
|
widthProp,
|
||||||
} = useGridlerStore((s) => ({
|
} = useGridlerStore((s) => ({
|
||||||
_gridSelection: s._gridSelection,
|
_gridSelection: s._gridSelection,
|
||||||
@ -62,6 +61,7 @@ export const GridlerDataGrid = () => {
|
|||||||
glideProps: s.glideProps,
|
glideProps: s.glideProps,
|
||||||
hasLocalData: s.hasLocalData,
|
hasLocalData: s.hasLocalData,
|
||||||
headerHeight: s.headerHeight,
|
headerHeight: s.headerHeight,
|
||||||
|
heightProp: s.height,
|
||||||
mounted: s.mounted,
|
mounted: s.mounted,
|
||||||
onCellEdited: s.onCellEdited,
|
onCellEdited: s.onCellEdited,
|
||||||
onColumnMoved: s.onColumnMoved,
|
onColumnMoved: s.onColumnMoved,
|
||||||
@ -79,7 +79,6 @@ export const GridlerDataGrid = () => {
|
|||||||
setState: s.setState,
|
setState: s.setState,
|
||||||
setStateFN: s.setStateFN,
|
setStateFN: s.setStateFN,
|
||||||
total_rows: s.total_rows,
|
total_rows: s.total_rows,
|
||||||
heightProp: s.height,
|
|
||||||
widthProp: s.width,
|
widthProp: s.width,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -209,7 +208,7 @@ export const GridlerDataGrid = () => {
|
|||||||
checkboxStyle: 'square',
|
checkboxStyle: 'square',
|
||||||
kind: 'both',
|
kind: 'both',
|
||||||
}}
|
}}
|
||||||
rows={total_rows}
|
rows={total_rows ?? 0}
|
||||||
rowSelect="multi"
|
rowSelect="multi"
|
||||||
rowSelectionMode="auto"
|
rowSelectionMode="auto"
|
||||||
spanRangeBehavior="default"
|
spanRangeBehavior="default"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useGridlerStore } from './Store';
|
import { useGridlerStore } from './GridlerStore';
|
||||||
|
|
||||||
export function BottomBar() {
|
export function BottomBar() {
|
||||||
const { _activeTooltip, tooltipBarProps } = useGridlerStore((s) => ({
|
const { _activeTooltip, tooltipBarProps } = useGridlerStore((s) => ({
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { useDebouncedValue } from '@mantine/hooks';
|
|||||||
import { IconX } from '@tabler/icons-react';
|
import { IconX } from '@tabler/icons-react';
|
||||||
import { type ReactNode, useEffect, useState } from 'react';
|
import { type ReactNode, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import type { FilterOption, FilterOptionOperator, GridlerStoreState } from './Store';
|
import type { FilterOption, FilterOptionOperator, GridlerStoreState } from './GridlerStore';
|
||||||
|
|
||||||
export type GridCellLoose = {
|
export type GridCellLoose = {
|
||||||
kind: GridCellKind | string;
|
kind: GridCellKind | string;
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
import { CompactSelection } from '@glideapps/glide-data-grid';
|
import { CompactSelection } from '@glideapps/glide-data-grid';
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { useGridlerStore } from './Store';
|
import { useGridlerStore } from './GridlerStore';
|
||||||
|
|
||||||
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
||||||
export const Computer = React.memo(() => {
|
export const Computer = React.memo(() => {
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
type Item,
|
type Item,
|
||||||
type Rectangle,
|
type Rectangle,
|
||||||
} from '@glideapps/glide-data-grid';
|
} from '@glideapps/glide-data-grid';
|
||||||
|
import { IconGrid4x4 } from '@tabler/icons-react';
|
||||||
import { getUUID } from '@warkypublic/artemis-kit';
|
import { getUUID } from '@warkypublic/artemis-kit';
|
||||||
import { getNestedValue } from '@warkypublic/artemis-kit/object';
|
import { getNestedValue } from '@warkypublic/artemis-kit/object';
|
||||||
import { createSyncStore } from '@warkypublic/zustandsyncstore';
|
import { createSyncStore } from '@warkypublic/zustandsyncstore';
|
||||||
@ -27,12 +28,10 @@ import {
|
|||||||
type MantineBetterMenuInstanceItem,
|
type MantineBetterMenuInstanceItem,
|
||||||
useMantineBetterMenus,
|
useMantineBetterMenus,
|
||||||
} from '../../MantineBetterMenu';
|
} from '../../MantineBetterMenu';
|
||||||
import { type FormRequestType } from '../utils/types';
|
|
||||||
import { ColumnFilterSet, type GridlerColumn, type GridlerColumns } from './Column';
|
import { ColumnFilterSet, type GridlerColumn, type GridlerColumns } from './Column';
|
||||||
import { SortDownSprite } from './sprites/SortDown';
|
import { SortDownSprite } from './sprites/SortDown';
|
||||||
import { SortUpSprite } from './sprites/SortUp';
|
import { SortUpSprite } from './sprites/SortUp';
|
||||||
import { SpriteImage } from './sprites/SpriteImage';
|
import { SpriteImage } from './sprites/SpriteImage';
|
||||||
import { IconGrid4x4 } from '@tabler/icons-react';
|
|
||||||
|
|
||||||
export type FilterOption = {
|
export type FilterOption = {
|
||||||
datatype?: 'array' | 'boolean' | 'date' | 'function' | 'number' | 'object' | 'string';
|
datatype?: 'array' | 'boolean' | 'date' | 'function' | 'number' | 'object' | 'string';
|
||||||
@ -99,6 +98,7 @@ export interface GridlerProps extends PropsWithChildren {
|
|||||||
selectMode?: 'cell' | 'row';
|
selectMode?: 'cell' | 'row';
|
||||||
showMenu?: (id: string, options?: Partial<MantineBetterMenuInstance>) => void;
|
showMenu?: (id: string, options?: Partial<MantineBetterMenuInstance>) => void;
|
||||||
tooltipBarProps?: React.HTMLAttributes<HTMLDivElement>;
|
tooltipBarProps?: React.HTMLAttributes<HTMLDivElement>;
|
||||||
|
total_rows?: number;
|
||||||
uniqueid: string;
|
uniqueid: string;
|
||||||
useAPIQuery?: (index: number) => Promise<Array<Record<string, any>>>;
|
useAPIQuery?: (index: number) => Promise<Array<Record<string, any>>>;
|
||||||
values?: Array<Record<string, any>>;
|
values?: Array<Record<string, any>>;
|
||||||
@ -177,7 +177,6 @@ export interface GridlerState {
|
|||||||
value: (current: GridlerStoreState[K]) => Partial<GridlerStoreState[K]>
|
value: (current: GridlerStoreState[K]) => Partial<GridlerStoreState[K]>
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
toCell: <TRowType extends Record<string, string>>(row: any, col: number) => GridCell;
|
toCell: <TRowType extends Record<string, string>>(row: any, col: number) => GridCell;
|
||||||
total_rows: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GridlerStoreState = GridlerProps & GridlerState;
|
export type GridlerStoreState = GridlerProps & GridlerState;
|
||||||
@ -850,10 +849,10 @@ const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreStat
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
hasLocalData: props.data && props.data.length > 0,
|
|
||||||
hideMenu: props.hideMenu ?? menus.hide,
|
hideMenu: props.hideMenu ?? menus.hide,
|
||||||
showMenu: props.showMenu ?? menus.show,
|
showMenu: props.showMenu ?? menus.show,
|
||||||
total_rows: props.data?.length ?? getState('total_rows'),
|
total_rows: props.total_rows ?? getState('total_rows') ?? 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -1,8 +1,7 @@
|
|||||||
|
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import { range } from '../utils/range';
|
import { range } from '../utils/range';
|
||||||
import { useGridlerStore } from './Store';
|
import { useGridlerStore } from './GridlerStore';
|
||||||
|
|
||||||
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
||||||
export const Pager = React.memo(() => {
|
export const Pager = React.memo(() => {
|
||||||
@ -14,7 +13,7 @@ export const Pager = React.memo(() => {
|
|||||||
pageSize,
|
pageSize,
|
||||||
loadPage,
|
loadPage,
|
||||||
_loadingList,
|
_loadingList,
|
||||||
hasLocalData
|
hasLocalData,
|
||||||
] = useGridlerStore((s) => [
|
] = useGridlerStore((s) => [
|
||||||
s.setState,
|
s.setState,
|
||||||
s._glideref,
|
s._glideref,
|
||||||
@ -24,17 +23,21 @@ export const Pager = React.memo(() => {
|
|||||||
s.loadPage,
|
s.loadPage,
|
||||||
|
|
||||||
s._loadingList,
|
s._loadingList,
|
||||||
s.hasLocalData
|
s.hasLocalData,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!glideref) {return;}
|
if (!glideref) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setState('mounted', true);
|
setState('mounted', true);
|
||||||
}, [setState]);
|
}, [setState]);
|
||||||
|
|
||||||
//Maybe move this into a computer component.
|
//Maybe move this into a computer component.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!glideref) {return;}
|
if (!glideref) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (hasLocalData) {
|
if (hasLocalData) {
|
||||||
//using local data, no need to load pages
|
//using local data, no need to load pages
|
||||||
return;
|
return;
|
||||||
@ -56,7 +59,7 @@ export const Pager = React.memo(() => {
|
|||||||
for (const page of range(firstPage, lastPage + 1, 1)) {
|
for (const page of range(firstPage, lastPage + 1, 1)) {
|
||||||
loadPage(page);
|
loadPage(page);
|
||||||
}
|
}
|
||||||
}, [loadPage, pageSize, visiblePages, glideref, _loadingList,hasLocalData]);
|
}, [loadPage, pageSize, visiblePages, glideref, _loadingList, hasLocalData]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { ActionIcon } from '@mantine/core';
|
import { ActionIcon } from '@mantine/core';
|
||||||
import { IconMenu2 } from '@tabler/icons-react';
|
import { IconMenu2 } from '@tabler/icons-react';
|
||||||
|
|
||||||
import { useGridlerStore } from './Store';
|
import { useGridlerStore } from './GridlerStore';
|
||||||
|
|
||||||
export function RightMenuIcon() {
|
export function RightMenuIcon() {
|
||||||
const { loadingData, onContextClick } = useGridlerStore((s) => ({
|
const { loadingData, onContextClick } = useGridlerStore((s) => ({
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import type { APIOptions } from '../utils/types';
|
import type { APIOptions } from '../../utils/types';
|
||||||
|
|
||||||
import { useGridlerStore } from './Store';
|
import { useGridlerStore } from '../GridlerStore';
|
||||||
|
|
||||||
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
||||||
export const APIAdaptorGoLangv2 = React.memo((props: APIOptions) => {
|
export const GlidlerAPIAdaptorForGoLangv2 = React.memo((props: APIOptions) => {
|
||||||
const [setStateFN, setState, getState, addError, mounted] = useGridlerStore((s) => [
|
const [setStateFN, setState, getState, addError, mounted] = useGridlerStore((s) => [
|
||||||
s.setStateFN,
|
s.setStateFN,
|
||||||
s.setState,
|
s.setState,
|
||||||
@ -7,13 +7,13 @@ import {
|
|||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import type { MantineBetterMenuInstanceItem } from '../../MantineBetterMenu';
|
import type { MantineBetterMenuInstanceItem } from '../../../MantineBetterMenu';
|
||||||
import type { FormRequestType } from '../utils/types';
|
import type { FormRequestType } from '../../utils/types';
|
||||||
import type { GridlerColumn } from './Column';
|
import type { GridlerColumn } from '../Column';
|
||||||
|
|
||||||
import { type GridlerProps, type GridlerState, useGridlerStore } from './Store';
|
import { type GridlerProps, type GridlerState, useGridlerStore } from '../GridlerStore';
|
||||||
|
|
||||||
export function GlidlerFormInterface(props: {
|
export function GlidlerFormAdaptor(props: {
|
||||||
descriptionField?: ((data: Record<string, unknown>) => string) | string;
|
descriptionField?: ((data: Record<string, unknown>) => string) | string;
|
||||||
getMenuItems?: GridlerProps['getMenuItems'];
|
getMenuItems?: GridlerProps['getMenuItems'];
|
||||||
onReload?: () => void;
|
onReload?: () => void;
|
||||||
@ -1,23 +1,14 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import type { APIOptions } from '../utils/types';
|
import { useGridlerStore } from '../GridlerStore';
|
||||||
|
|
||||||
import { useGridlerStore } from './Store';
|
export interface GlidlerLocalDataAdaptorProps {
|
||||||
|
|
||||||
interface LocalDataAdaptorProps {
|
|
||||||
data: Array<unknown>;
|
data: Array<unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
||||||
export const LocalDataAdaptor = React.memo((props: LocalDataAdaptorProps) => {
|
export const GlidlerLocalDataAdaptor = React.memo((props: GlidlerLocalDataAdaptorProps) => {
|
||||||
const [setStateFN, setState, getState, addError, mounted] = useGridlerStore((s) => [
|
const [setState, getState, mounted] = useGridlerStore((s) => [s.setState, s.getState, s.mounted]);
|
||||||
s.setStateFN,
|
|
||||||
s.setState,
|
|
||||||
s.getState,
|
|
||||||
s.addError,
|
|
||||||
s.mounted,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const useAPIQuery: (index: number) => Promise<any> = async (index: number) => {
|
const useAPIQuery: (index: number) => Promise<any> = async (index: number) => {
|
||||||
const pageSize = getState('pageSize');
|
const pageSize = getState('pageSize');
|
||||||
3
src/Gridler/components/adaptors/index.ts
Normal file
3
src/Gridler/components/adaptors/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './GlidlerAPIAdaptorForGoLangv2'
|
||||||
|
export * from './GlidlerFormAdaptor'
|
||||||
|
export * from './GlidlerLocalDataAdaptor'
|
||||||
@ -3,7 +3,7 @@ import type { GetRowThemeCallback } from '@glideapps/glide-data-grid';
|
|||||||
import { darken, lighten, useMantineColorScheme, useMantineTheme } from '@mantine/core';
|
import { darken, lighten, useMantineColorScheme, useMantineTheme } from '@mantine/core';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useGridlerStore } from '../components/Store';
|
import { useGridlerStore } from '../components/GridlerStore';
|
||||||
|
|
||||||
export const offsetRows = (colorScheme: any) => (i: number) =>
|
export const offsetRows = (colorScheme: any) => (i: number) =>
|
||||||
i % 2 === 0
|
i % 2 === 0
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
export {GlidlerAPIAdaptorForGoLangv2 } from './components/adaptors/GlidlerAPIAdaptorForGoLangv2'
|
||||||
|
export {GlidlerFormAdaptor } from './components/adaptors/GlidlerFormAdaptor'
|
||||||
|
export {GlidlerLocalDataAdaptor } from './components/adaptors/GlidlerLocalDataAdaptor'
|
||||||
export * from './components/Column'
|
export * from './components/Column'
|
||||||
export {useGridlerStore } from './components/Store'
|
export {useGridlerStore } from './components/GridlerStore'
|
||||||
export {Gridler} from './Gridler'
|
export {Gridler} from './Gridler'
|
||||||
@ -4,7 +4,7 @@ import { useState } from 'react';
|
|||||||
|
|
||||||
import type { GridlerColumns } from '../components/Column';
|
import type { GridlerColumns } from '../components/Column';
|
||||||
|
|
||||||
import { APIAdaptorGoLangv2 } from '../components/APIAdaptorGoLangv2';
|
import { GlidlerAPIAdaptorForGoLangv2 } from '../components/adaptors';
|
||||||
import { Gridler } from '../Gridler';
|
import { Gridler } from '../Gridler';
|
||||||
|
|
||||||
export const GridlerGoAPIExampleEventlog = () => {
|
export const GridlerGoAPIExampleEventlog = () => {
|
||||||
@ -51,9 +51,10 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
<TextInput label="API Key" onChange={(e) => setApiKey(e.target.value)} value={apiKey} />
|
<TextInput label="API Key" onChange={(e) => setApiKey(e.target.value)} value={apiKey} />
|
||||||
<Divider />
|
<Divider />
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Show Side Sections"
|
|
||||||
checked={!!sections}
|
checked={!!sections}
|
||||||
|
label="Show Side Sections"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
e.target.checked
|
e.target.checked
|
||||||
? setSections({
|
? setSections({
|
||||||
bottom: <div style={{ backgroundColor: 'teal', height: '25px' }}>bottom</div>,
|
bottom: <div style={{ backgroundColor: 'teal', height: '25px' }}>bottom</div>,
|
||||||
@ -66,8 +67,8 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Gridler
|
<Gridler
|
||||||
height="100%"
|
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
height="100%"
|
||||||
// getMenuItems={(id, _state, row, col, defaultItems) => {
|
// getMenuItems={(id, _state, row, col, defaultItems) => {
|
||||||
// console.log('GridlerGoAPIExampleEventlog getMenuItems root', id, row, col, defaultItems);
|
// console.log('GridlerGoAPIExampleEventlog getMenuItems root', id, row, col, defaultItems);
|
||||||
// return [
|
// return [
|
||||||
@ -92,8 +93,8 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
uniqueid="gridtest"
|
uniqueid="gridtest"
|
||||||
values={values}
|
values={values}
|
||||||
>
|
>
|
||||||
<Gridler.APIAdaptorGoLangv2 authtoken={apiKey} url={`${apiUrl}/public/process`} />
|
<GlidlerAPIAdaptorForGoLangv2 authtoken={apiKey} url={`${apiUrl}/public/process`} />
|
||||||
<Gridler.GlidlerFormInterface
|
<Gridler.FormAdaptor
|
||||||
descriptionField={'process'}
|
descriptionField={'process'}
|
||||||
onRequestForm={(request, data) => {
|
onRequestForm={(request, data) => {
|
||||||
console.log('Form requested', request, data);
|
console.log('Form requested', request, data);
|
||||||
|
|||||||
@ -1,19 +1,17 @@
|
|||||||
import { MantineProvider } from '@mantine/core'
|
import { MantineProvider } from '@mantine/core';
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react';
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { MantineBetterMenusProvider } from './MantineBetterMenu'
|
import { MantineBetterMenusProvider } from './MantineBetterMenu';
|
||||||
|
|
||||||
// Mock the MenuRenderer component since it likely has complex portal logic
|
// Mock the MenuRenderer component since it likely has complex portal logic
|
||||||
vi.mock('./MenuRenderer', () => ({
|
vi.mock('./MenuRenderer', () => ({
|
||||||
MenuRenderer: () => <div data-testid="menu-renderer">Menu Renderer</div>
|
MenuRenderer: () => <div data-testid="menu-renderer">Menu Renderer</div>,
|
||||||
}))
|
}));
|
||||||
|
|
||||||
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||||
<MantineProvider>
|
<MantineProvider>{children}</MantineProvider>
|
||||||
{children}
|
);
|
||||||
</MantineProvider>
|
|
||||||
)
|
|
||||||
|
|
||||||
describe('MantineBetterMenusProvider', () => {
|
describe('MantineBetterMenusProvider', () => {
|
||||||
it('renders children correctly', () => {
|
it('renders children correctly', () => {
|
||||||
@ -23,10 +21,10 @@ describe('MantineBetterMenusProvider', () => {
|
|||||||
<div data-testid="test-child">Test Child</div>
|
<div data-testid="test-child">Test Child</div>
|
||||||
</MantineBetterMenusProvider>
|
</MantineBetterMenusProvider>
|
||||||
</TestWrapper>
|
</TestWrapper>
|
||||||
)
|
);
|
||||||
|
|
||||||
expect(screen.getByTestId('test-child')).toBeInTheDocument()
|
expect(screen.getByTestId('test-child')).toBeInTheDocument();
|
||||||
})
|
});
|
||||||
|
|
||||||
it('renders MenuRenderer component', () => {
|
it('renders MenuRenderer component', () => {
|
||||||
render(
|
render(
|
||||||
@ -35,10 +33,10 @@ describe('MantineBetterMenusProvider', () => {
|
|||||||
<div>Test</div>
|
<div>Test</div>
|
||||||
</MantineBetterMenusProvider>
|
</MantineBetterMenusProvider>
|
||||||
</TestWrapper>
|
</TestWrapper>
|
||||||
)
|
);
|
||||||
|
|
||||||
expect(screen.getByTestId('menu-renderer')).toBeInTheDocument()
|
expect(screen.getByTestId('menu-renderer')).toBeInTheDocument();
|
||||||
})
|
});
|
||||||
|
|
||||||
it('accepts providerID prop', () => {
|
it('accepts providerID prop', () => {
|
||||||
render(
|
render(
|
||||||
@ -47,9 +45,9 @@ describe('MantineBetterMenusProvider', () => {
|
|||||||
<div>Test</div>
|
<div>Test</div>
|
||||||
</MantineBetterMenusProvider>
|
</MantineBetterMenusProvider>
|
||||||
</TestWrapper>
|
</TestWrapper>
|
||||||
)
|
);
|
||||||
|
|
||||||
// Component should render without errors
|
// Component should render without errors
|
||||||
expect(screen.getByTestId('menu-renderer')).toBeInTheDocument()
|
expect(screen.getByTestId('menu-renderer')).toBeInTheDocument();
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { MenuRenderer } from './MenuRenderer';
|
import { MenuRenderer } from './MenuRenderer';
|
||||||
import { MantineBetterMenusStoreProvider, type MenuStoreProps } from './Store';
|
import { MantineBetterMenusStoreProvider, type MantineBetterMenuStoreProps } from './Store';
|
||||||
|
|
||||||
export function MantineBetterMenusProvider(props: React.PropsWithChildren<MenuStoreProps>) {
|
export function MantineBetterMenusProvider(
|
||||||
|
props: React.PropsWithChildren<MantineBetterMenuStoreProps>
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<MantineBetterMenusStoreProvider {...props}>
|
<MantineBetterMenusStoreProvider {...props}>
|
||||||
<MenuRenderer />
|
<MenuRenderer />
|
||||||
|
|||||||
@ -1,100 +1,98 @@
|
|||||||
import { act, renderHook } from '@testing-library/react'
|
import { act, renderHook } from '@testing-library/react';
|
||||||
import { beforeEach, describe, expect, it } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { MantineBetterMenusStoreProvider, useMantineBetterMenus } from './Store'
|
import { MantineBetterMenusStoreProvider, useMantineBetterMenus } from './Store';
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
vi.mock('@warkypublic/artemis-kit', () => ({
|
vi.mock('@warkypublic/artemis-kit', () => ({
|
||||||
getUUID: () => 'test-uuid-123'
|
getUUID: () => 'test-uuid-123',
|
||||||
}))
|
}));
|
||||||
|
|
||||||
const createWrapper = () => {
|
const createWrapper = () => {
|
||||||
return ({ children }: { children: React.ReactNode }) => (
|
return ({ children }: { children: React.ReactNode }) => (
|
||||||
<MantineBetterMenusStoreProvider>
|
<MantineBetterMenusStoreProvider>{children}</MantineBetterMenusStoreProvider>
|
||||||
{children}
|
);
|
||||||
</MantineBetterMenusStoreProvider>
|
};
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('MantineBetterMenus Store', () => {
|
describe('MantineBetterMenus Store', () => {
|
||||||
let wrapper: ReturnType<typeof createWrapper>
|
let wrapper: ReturnType<typeof createWrapper>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = createWrapper()
|
wrapper = createWrapper();
|
||||||
})
|
});
|
||||||
|
|
||||||
it('initializes with empty menus array', () => {
|
it('initializes with empty menus array', () => {
|
||||||
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper })
|
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper });
|
||||||
|
|
||||||
expect(result.current.menus).toEqual([])
|
expect(result.current.menus).toEqual([]);
|
||||||
})
|
});
|
||||||
|
|
||||||
it('can show a menu', () => {
|
it('can show a menu', () => {
|
||||||
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper })
|
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper });
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.show('test-menu', {
|
result.current.show('test-menu', {
|
||||||
items: [{ label: 'Test Item' }],
|
items: [{ label: 'Test Item' }],
|
||||||
x: 100,
|
x: 100,
|
||||||
y: 200
|
y: 200,
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
expect(result.current.menus).toHaveLength(1)
|
expect(result.current.menus).toHaveLength(1);
|
||||||
expect(result.current.menus[0]).toMatchObject({
|
expect(result.current.menus[0]).toMatchObject({
|
||||||
id: 'test-menu',
|
id: 'test-menu',
|
||||||
items: [{ label: 'Test Item' }],
|
items: [{ label: 'Test Item' }],
|
||||||
visible: true,
|
visible: true,
|
||||||
x: 100,
|
x: 100,
|
||||||
y: 200
|
y: 200,
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
it('can hide a menu', () => {
|
it('can hide a menu', () => {
|
||||||
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper })
|
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper });
|
||||||
|
|
||||||
// First show a menu
|
// First show a menu
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.show('test-menu', { x: 100, y: 200 })
|
result.current.show('test-menu', { x: 100, y: 200 });
|
||||||
})
|
});
|
||||||
|
|
||||||
expect(result.current.menus[0].visible).toBe(true)
|
expect(result.current.menus[0].visible).toBe(true);
|
||||||
|
|
||||||
// Then hide it
|
// Then hide it
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.hide('test-menu')
|
result.current.hide('test-menu');
|
||||||
})
|
});
|
||||||
|
|
||||||
expect(result.current.menus[0].visible).toBe(false)
|
expect(result.current.menus[0].visible).toBe(false);
|
||||||
})
|
});
|
||||||
|
|
||||||
it('can update instance state', () => {
|
it('can update instance state', () => {
|
||||||
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper })
|
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper });
|
||||||
|
|
||||||
// Show a menu
|
// Show a menu
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.show('test-menu', { x: 100, y: 200 })
|
result.current.show('test-menu', { x: 100, y: 200 });
|
||||||
})
|
});
|
||||||
|
|
||||||
// Update its position
|
// Update its position
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.setInstanceState('test-menu', 'x', 300)
|
result.current.setInstanceState('test-menu', 'x', 300);
|
||||||
})
|
});
|
||||||
|
|
||||||
expect(result.current.menus[0].x).toBe(300)
|
expect(result.current.menus[0].x).toBe(300);
|
||||||
expect(result.current.menus[0].y).toBe(200) // Should remain unchanged
|
expect(result.current.menus[0].y).toBe(200); // Should remain unchanged
|
||||||
})
|
});
|
||||||
|
|
||||||
it('handles multiple menus', () => {
|
it('handles multiple menus', () => {
|
||||||
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper })
|
const { result } = renderHook(() => useMantineBetterMenus(), { wrapper });
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.show('menu-1', { x: 100, y: 200 })
|
result.current.show('menu-1', { x: 100, y: 200 });
|
||||||
result.current.show('menu-2', { x: 300, y: 400 })
|
result.current.show('menu-2', { x: 300, y: 400 });
|
||||||
})
|
});
|
||||||
|
|
||||||
expect(result.current.menus).toHaveLength(2)
|
expect(result.current.menus).toHaveLength(2);
|
||||||
expect(result.current.menus.find(m => m.id === 'menu-1')).toBeDefined()
|
expect(result.current.menus.find((m) => m.id === 'menu-1')).toBeDefined();
|
||||||
expect(result.current.menus.find(m => m.id === 'menu-2')).toBeDefined()
|
expect(result.current.menus.find((m) => m.id === 'menu-2')).toBeDefined();
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@ -28,15 +28,16 @@ export interface MantineBetterMenuInstanceItem extends Partial<MenuItemProps> {
|
|||||||
| ReactNode;
|
| ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MenuStoreProps {
|
export interface MantineBetterMenuStoreProps {
|
||||||
menus?: Array<MantineBetterMenuInstance>;
|
menus?: Array<MantineBetterMenuInstance>;
|
||||||
providerID?: string;
|
providerID?: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MenuStoreState = MenuStoreProps & MenuStoreStateOnly;
|
export type MantineBetterMenuStoreState = MantineBetterMenuStoreProps &
|
||||||
|
MantineBetterMenuStoreStateOnly;
|
||||||
|
|
||||||
export interface MenuStoreStateOnly {
|
export interface MantineBetterMenuStoreStateOnly {
|
||||||
hide: (id: string) => void;
|
hide: (id: string) => void;
|
||||||
menus: Array<MantineBetterMenuInstance>;
|
menus: Array<MantineBetterMenuInstance>;
|
||||||
setInstanceState: <K extends keyof MantineBetterMenuInstance>(
|
setInstanceState: <K extends keyof MantineBetterMenuInstance>(
|
||||||
@ -44,12 +45,15 @@ export interface MenuStoreStateOnly {
|
|||||||
key: K,
|
key: K,
|
||||||
value: MantineBetterMenuInstance[K]
|
value: MantineBetterMenuInstance[K]
|
||||||
) => void;
|
) => void;
|
||||||
setState: <K extends keyof MenuStoreState>(key: K, value: Partial<MenuStoreState[K]>) => void;
|
setState: <K extends keyof MantineBetterMenuStoreState>(
|
||||||
|
key: K,
|
||||||
|
value: Partial<MantineBetterMenuStoreState[K]>
|
||||||
|
) => void;
|
||||||
show: (id: string, options?: Partial<MantineBetterMenuInstance>) => void;
|
show: (id: string, options?: Partial<MantineBetterMenuInstance>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Provider: MantineBetterMenusStoreProvider, useStore: useMantineBetterMenus } =
|
const { Provider: MantineBetterMenusStoreProvider, useStore: useMantineBetterMenus } =
|
||||||
createSyncStore<MenuStoreState, MenuStoreProps>(
|
createSyncStore<MantineBetterMenuStoreState, MantineBetterMenuStoreProps>(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
hide: (id: string) => {
|
hide: (id: string) => {
|
||||||
const s = get();
|
const s = get();
|
||||||
@ -59,7 +63,7 @@ const { Provider: MantineBetterMenusStoreProvider, useStore: useMantineBetterMen
|
|||||||
setInstanceState: (id, key, value) => {
|
setInstanceState: (id, key, value) => {
|
||||||
//@ts-expect-error Type instantiation is excessively deep and possibly infinite.
|
//@ts-expect-error Type instantiation is excessively deep and possibly infinite.
|
||||||
set(
|
set(
|
||||||
produce((state: MenuStoreState) => {
|
produce((state: MantineBetterMenuStoreState) => {
|
||||||
const idx = state?.menus?.findIndex((m: MantineBetterMenuInstance) => m.id === id);
|
const idx = state?.menus?.findIndex((m: MantineBetterMenuInstance) => m.id === id);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
state.menus[idx][key] = value;
|
state.menus[idx][key] = value;
|
||||||
@ -89,7 +93,7 @@ const { Provider: MantineBetterMenusStoreProvider, useStore: useMantineBetterMen
|
|||||||
s.setState('menus', [...s.menus, menu as MantineBetterMenuInstance]);
|
s.setState('menus', [...s.menus, menu as MantineBetterMenuInstance]);
|
||||||
} else {
|
} else {
|
||||||
set(
|
set(
|
||||||
produce((state: MenuStoreState) => {
|
produce((state: MantineBetterMenuStoreState) => {
|
||||||
if (!state.menus) {
|
if (!state.menus) {
|
||||||
state.menus = [];
|
state.menus = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,5 +3,5 @@ export { useMantineBetterMenus } from './Store';
|
|||||||
export type {
|
export type {
|
||||||
MantineBetterMenuInstance,
|
MantineBetterMenuInstance,
|
||||||
MantineBetterMenuInstanceItem,
|
MantineBetterMenuInstanceItem,
|
||||||
MenuStoreState
|
MantineBetterMenuStoreState
|
||||||
} from './Store';
|
} from './Store';
|
||||||
|
|||||||
@ -5,6 +5,6 @@ export {
|
|||||||
type MantineBetterMenuInstance,
|
type MantineBetterMenuInstance,
|
||||||
type MantineBetterMenuInstanceItem,
|
type MantineBetterMenuInstanceItem,
|
||||||
MantineBetterMenusProvider,
|
MantineBetterMenusProvider,
|
||||||
type MenuStoreState,
|
type MantineBetterMenuStoreState,
|
||||||
useMantineBetterMenus,
|
useMantineBetterMenus,
|
||||||
} from "./MantineBetterMenu";
|
} from "./MantineBetterMenu";
|
||||||
Loading…
Reference in New Issue
Block a user