Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8cc3097
feat(iModelGrid): add sort state persistence to IModelTable and IMode…
arome Jul 10, 2026
137e2f6
feat(iTwinTable): add sort state persistence to ITwinTable and ITwinG…
arome Jul 10, 2026
5eb72db
feat: implement controlled sort model for IModelGrid and ITwinGrid co…
arome Jul 10, 2026
553f764
feat(iModelBrowser): add controlled table sorting for iTwin/iModel ta…
arome Jul 10, 2026
e279c84
feat: enhance sorting functionality in IModelGrid components
arome Jul 30, 2026
9342a06
feat: enhance sorting capabilities for IModel and ITwin tables with n…
arome Jul 30, 2026
7896e66
Merge remote-tracking branch 'origin/main' into omar/save-sort-imodel…
arome Jul 31, 2026
13484ef
fix: update TypedGridSortItem to use GridSortModel for improved type …
arome Jul 31, 2026
69f0806
feat: add Jest virtual folders and update TypeScript configurations f…
arome Jul 31, 2026
72a569d
Update omar-save-sort-imodel-table_2026-07-10-18-00.json
arome Jul 31, 2026
367c765
feat: enhance sorting functionality in ITwinGrid and IModelGrid compo…
arome Aug 3, 2026
bb84e3c
feat: add sorting by created date to IModelGrid components and update…
arome Aug 3, 2026
8742b39
revert changes
arome Aug 3, 2026
98a338f
feat: add support for sorting by createdDateTime in useIModelSort hoo…
arome Aug 3, 2026
d4f134e
feat: update sorting functionality in IModelGrid and ITwinGrid compon…
arome Aug 3, 2026
01e1d56
feat: enhance sorting functionality in IModelGrid and ITwinGrid compo…
arome Aug 5, 2026
a154fa5
feat: refactor sorting implementation in IModelGrid and ITwinGrid com…
arome Aug 5, 2026
467fc86
revert to single sorting for mui grid
arome Aug 5, 2026
4a23a21
feat: enhance client-side sorting to support dynamic sort types in cl…
arome Aug 5, 2026
7110959
feat: refactor sorting implementation in ITwinTableMUI to use sortOpt…
arome Aug 5, 2026
849d44d
Merge branch 'main' into omar/save-sort-imodel-table
arome Aug 5, 2026
64f0c6b
Update packages/modules/imodel-browser/src/mui/containers/iModelGrid/…
arome Aug 7, 2026
7a4c61f
fix: update onSortOptionsChange to require sortOptions parameter in I…
arome Aug 7, 2026
2e9b176
feat: implement client-side sorting for ITwinGrid with support for fa…
arome Aug 7, 2026
a92d7df
Merge branch 'main' into omar/save-sort-imodel-table
arome Aug 13, 2026
ed4547c
Merge branch 'main' into omar/save-sort-imodel-table
arome Aug 20, 2026
e76648e
Update packages/modules/imodel-browser/src/mui/containers/iModelGrid/…
arome Aug 24, 2026
6d55358
Update packages/modules/imodel-browser/src/mui/containers/iModelGrid/…
arome Aug 24, 2026
4ffa053
Refactor IModelGridProps comments for clarity and update sortOptions …
arome Aug 24, 2026
aade215
Improve comment formatting for onSortOptionsChange in ITwinGridMUI an…
arome Aug 24, 2026
6ebaaa0
Merge branch 'main' into omar/save-sort-imodel-table
arome Sep 2, 2026
3544978
Merge branch 'main' into omar/save-sort-imodel-table
arome Sep 11, 2026
1cd7462
Merge branch 'main' into omar/save-sort-imodel-table
arome Sep 21, 2026
7a19a86
Merge branch 'main' into omar/save-sort-imodel-table
arome Sep 24, 2026
3e21cf3
Revert formatting changes in create-imodel, delete-imodel, delete-itw…
arome Oct 2, 2026
420fb54
Revert changes in addon/preset.js and .storybook/itwinTheme.js
arome Oct 2, 2026
0a9c0ea
Revert import changes in ManageVersions stories
arome Oct 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,27 @@
"search.exclude": {
"**/CHANGELOG.md": true
},
"js/ts.preferences.autoImportSpecifierExcludeRegexes": ["^@mui/[^/]+$"]
"js/ts.preferences.autoImportSpecifierExcludeRegexes": ["^@mui/[^/]+$"],
"jest.virtualFolders": [
{
"name": "create-imodel",
"rootPath": "packages/modules/create-imodel"
},
{
"name": "delete-imodel",
"rootPath": "packages/modules/delete-imodel"
},
{
"name": "delete-itwin",
"rootPath": "packages/modules/delete-itwin"
},
{
"name": "imodel-browser",
"rootPath": "packages/modules/imodel-browser"
},
{
"name": "manage-versions",
"rootPath": "packages/modules/manage-versions"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/imodel-browser-react",
"comment": "Controlled table sorting for iTwin/iModel grids",
"type": "minor"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should've been marked as a major change since you do remove props from the Grid components

}
],
"packageName": "@itwin/imodel-browser-react"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Avatar from "@mui/material/Avatar";
import AvatarGroup from "@mui/material/AvatarGroup";
import Box from "@mui/material/Box";
import Chip from "@mui/material/Chip";
import { action } from "storybook/actions";
import type { Meta, StoryObj } from "@storybook/react-webpack5";
import svgGeo from "@stratakit/icons/geospatial-features.svg";
import svgPin from "@stratakit/icons/pin.svg";
Expand All @@ -16,6 +15,7 @@ import svgStatusError from "@stratakit/icons/status-error.svg";
import svgStatusSuccess from "@stratakit/icons/status-success.svg";
import svgStatusWarning from "@stratakit/icons/status-warning.svg";
import React from "react";
import { action } from "storybook/actions";

import {
BaseCard,
Expand Down Expand Up @@ -255,7 +255,11 @@ export const OpenSitePlus: StoryObj<typeof BaseCardStory> = {
icon: svgGeo,
},
{ key: "share", label: "Share", onClick: action("menu: share clicked") },
{ key: "delete", label: "Delete", onClick: action("menu: delete clicked") },
{
key: "delete",
label: "Delete",
onClick: action("menu: delete clicked"),
},
],
actions: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import {
type IModelFull,
type IModelGridProps as IModelGridMUIProps,
type IModelSortOptionsMUI,
DataStatus,
IModelCellColumn,
IModelGrid as ExternalComponent,
Expand Down Expand Up @@ -69,7 +70,6 @@ export default {

const baseArgs: IModelGridMUIProps = {
apiOverrides: { serverEnvironmentPrefix: "qa" },
sortOptions: { sortType: "name", descending: false },
actions: [
{
key: "open",
Expand All @@ -91,6 +91,7 @@ const baseArgs: IModelGridMUIProps = {
onClick: (iModel) => action("Details for " + iModel?.displayName)(iModel),
},
],
onSortOptionsChange: undefined,
};

export const Primary: StoryObj<typeof IModelGridMUI> = {
Expand Down Expand Up @@ -153,6 +154,91 @@ export const TableViewWithOverrides: StoryObj<typeof IModelGridMUI> = {
},
};

const TableWithControlledSortRender = (args: IModelGridMUIProps) => {
const [sortOptions, setSortOptions] = React.useState<
IModelSortOptionsMUI | undefined
>({ field: "name", direction: "desc" });
const field = sortOptions?.field;
const direction = sortOptions?.direction ?? "asc";

return (
<div>
<div
style={{
display: "flex",
gap: 8,
marginBottom: 8,
alignItems: "center",
}}
>
<Typography variant="body2">Sort by:</Typography>
<Chip
label="Name"
clickable
variant={field === "name" ? "filled" : "outlined"}
onClick={() => setSortOptions({ field: "name", direction })}
/>
<Chip
label="Last Modified"
clickable
variant={
field === "lastChangesetPushDateTime" ? "filled" : "outlined"
}
onClick={() =>
setSortOptions({ field: "lastChangesetPushDateTime", direction })
}
/>
<Chip
label="Created Date"
clickable
variant={field === "createdDateTime" ? "filled" : "outlined"}
onClick={() =>
setSortOptions({ field: "createdDateTime", direction })
}
/>
<Chip
label={direction === "desc" ? "↓ Descending" : "↑ Ascending"}
clickable
variant="outlined"
onClick={() =>
setSortOptions(
(prev) =>
prev && {
...prev,
direction: prev.direction === "desc" ? "asc" : "desc",
}
)
}
/>
</div>
<ExternalComponent
{...args}
sortOptions={sortOptions}
onSortOptionsChange={(newSortOptions) => {
action("sort options changed")(newSortOptions);
setSortOptions(newSortOptions);
}}
/>
</div>
);
};

export const TableWithControlledSort: StoryObj<typeof IModelGridMUI> = {
Comment thread
arome marked this conversation as resolved.
render: (args) => <TableWithControlledSortRender {...args} />,
args: {
...baseArgs,
viewMode: "cells",
},
parameters: {
docs: {
description: {
story:
"The sort state is fully controlled by the parent via `sortOptions` and `onSortOptionsChange`, so it can be saved anywhere the consumer wants.",
},
},
},
};

const OverrideApiDataWithLoadMoreRender = (args: IModelGridMUIProps) => {
const [data, setData] = React.useState<IModelFull[]>(initialData);
const [isLoading, setIsLoading] = React.useState(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import Avatar from "@mui/material/Avatar";
import AvatarGroup from "@mui/material/AvatarGroup";
import Box from "@mui/material/Box";
import Chip from "@mui/material/Chip";
import { action } from "storybook/actions";
import type { Meta, StoryObj } from "@storybook/react-webpack5";
import SvgDelete from "@stratakit/icons/delete.svg";
import svgRoad from "@stratakit/icons/road.svg";
import SvgShare from "@stratakit/icons/share.svg";
import React from "react";
import { action } from "storybook/actions";

import bridgeThumbnail from "../../utils/bridge.jpg";
import overpassThumbnail from "../../utils/overpass.jpg";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type ITwinDataState,
type ITwinFull,
type ITwinGridProps,
type ITwinSortOptionsMUI,
DataStatus,
ITwinCellColumn,
ITwinGrid as ExternalComponent,
Expand Down Expand Up @@ -47,6 +48,7 @@ const baseArgs: ITwinGridProps = {
onClick: (iTwin) => action("Open " + iTwin.displayName)(iTwin),
},
],
onSortOptionsChange: undefined,
};

export const Primary: StoryObj<typeof ITwinGrid> = {
Expand Down Expand Up @@ -120,6 +122,84 @@ export const TableViewWithOverrides: StoryObj<typeof ITwinGrid> = {
},
};

const TableWithControlledSortRender = (args: ITwinGridProps) => {
const [sortOptions, setSortOptions] = React.useState<
ITwinSortOptionsMUI | undefined
>({ field: "number", direction: "desc" });
const field = sortOptions?.field;
const direction = sortOptions?.direction ?? "asc";

return (
<div>
<div
style={{
display: "flex",
gap: 8,
marginBottom: 8,
alignItems: "center",
}}
>
<Typography variant="body2">Sort by:</Typography>
<Chip
label="iTwin Number"
clickable
variant={field === "number" ? "filled" : "outlined"}
onClick={() => setSortOptions({ field: "number", direction })}
/>
<Chip
label="iTwin Name"
clickable
variant={field === "displayName" ? "filled" : "outlined"}
onClick={() => setSortOptions({ field: "displayName", direction })}
/>
<Chip
label="Last Modified"
clickable
variant={field === "lastModifiedDateTime" ? "filled" : "outlined"}
onClick={() =>
setSortOptions({ field: "lastModifiedDateTime", direction })
}
/>
<Chip
label={direction === "desc" ? "↓ Descending" : "↑ Ascending"}
clickable
variant="outlined"
onClick={() =>
setSortOptions(
(prev) =>
prev && {
...prev,
direction: prev.direction === "desc" ? "asc" : "desc",
}
)
}
/>
</div>
<ExternalComponent
{...args}
sortOptions={sortOptions}
onSortOptionsChange={(newSortOptions) => {
action("sort options changed")(newSortOptions);
setSortOptions(newSortOptions);
}}
/>
</div>
);
};

export const TableWithControlledSort: StoryObj<typeof ITwinGrid> = {
render: (args) => <TableWithControlledSortRender {...args} />,
args: { ...baseArgs, viewMode: "cells" },
parameters: {
docs: {
description: {
story:
"The sort state is fully controlled by the parent via `sortOptions` and `onSortOptionsChange`, so it can be saved anywhere the consumer wants.",
},
},
},
};

export const OverrideApiData: StoryObj<typeof ITwinGrid> = {
args: {
...baseArgs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import {
import { SvgThumbnail } from "@itwin/imodel-browser-react/mui";
import Box from "@mui/material/Box";
import Chip from "@mui/material/Chip";
import { action } from "storybook/actions";
import type { Meta, StoryObj } from "@storybook/react-webpack5";
import svgMagnet from "@stratakit/icons/magnet.svg";
import React from "react";
import { action } from "storybook/actions";

import { DefaultThumbnail } from "../../../../../modules/imodel-browser/src/mui/containers/ITwinGrid/ITwinTileMUI";
import bridgeThumbnail from "../../utils/bridge.jpg";
Expand Down
1 change: 1 addition & 0 deletions packages/modules/imodel-browser/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
],
},
transformIgnorePatterns: [
"<rootDir>/scripts/setupJest\\.js$",
"node_modules/(?!(\\.pnpm/(@stratakit|@ariakit|@mui).*|@bentley/ui|@stratakit|@ariakit|@mui))",
"^.+\\.module\\.(css|sass|scss)$",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ export interface IModelGridProps {
fetchStatus: DataStatus | undefined,
searchText: string | undefined
) => IModelFull[];
/**Component to show when there is no iModel */
/** Component to show when there is no iModel */
emptyStateComponent?: React.ReactNode;
/** Exact name of the iModel to display */
/** Exact name of the iModel to display */
searchText?: string;
/**iModel view mode */
/** iModel view mode */
viewMode?: ViewType;
/** The maximum number of iModels that can be fetched per request */
pageSize?: number;
Expand Down Expand Up @@ -226,7 +226,12 @@ const IModelGridInternal = ({
const [sort, setSort] = React.useState<IModelSortOptions>(sortOptions);

React.useEffect(() => {
setSort(calculateSort(viewMode, sortOptions));
setSort(
calculateSort(viewMode, {
descending: sortOptions.descending,
sortType: sortOptions.sortType,
})
);
}, [sortOptions.descending, sortOptions.sortType, viewMode]);

const strings = generateStrings(requestType, stringsOverrides);
Expand Down
Loading
Loading