=
+ withAccessTokenOverride((args) => {
+ const addStartTile = React.useCallback(
+ (iTwins: ITwinFull[], status: any) => {
+ if (status !== (DataStatus as any).Complete) {
+ return iTwins;
+ }
+ iTwins.unshift({
+ id: "newProject",
+ displayName: "New Project",
+ number: "Click on this tile to create a new ITwin",
+ });
+ return iTwins;
+ },
+ []
+ );
+ return (
+
+
+ Property postProcessCallback allows modification of the
+ data that is sent to the grid, here, we add a new tile at the start of
+ the list for a 'New Project'.
+
+
+
+ );
+ });
+WithPostProcessCallback.args = {
+ apiOverrides: { serverEnvironmentPrefix: "qa" },
+};
+
+export const FetchAllSubclasses = Template.bind({});
+FetchAllSubclasses.args = {
+ apiOverrides: { serverEnvironmentPrefix: "qa" },
+ iTwinSubClass: "All",
+};
+
+export const NoResultsWithDefaultEmptyState = Template.bind({});
+NoResultsWithDefaultEmptyState.args = {
+ ...baseArgs,
+ apiOverrides: { serverEnvironmentPrefix: "qa" },
+ postProcessCallback: (iModels, status) => {
+ return [];
+ },
+};
+
+export const StringsOverrideGrid = Template.bind({});
+StringsOverrideGrid.args = {
+ ...baseArgs,
+ apiOverrides: {
+ data: [
+ {
+ id: "1",
+ displayName: "Bridge iTwin",
+ number: "1111-2222-3333-4444",
+ image: bridgeThumbnail,
+ status: "Trial",
+ },
+ {
+ id: "2",
+ displayName: "Power iTwin",
+ number: "2222-3333-4444-5555",
+ image: powerThumbnail,
+ status: "Inactive",
+ },
+ {
+ id: "3",
+ displayName: "Highway iTwin",
+ number: "3333-4444-5555-6666",
+ image: nightThumbnail,
+ },
+ ],
+ },
+ iTwinActions: [
+ {
+ children: "Some action",
+ key: "something",
+ onClick: (iTwin) => action("clicked " + iTwin?.displayName)(iTwin),
+ },
+ ],
+ stringsOverrides: {
+ moreOptions: "Flere muligheder",
+ trialBadge: "Prøveversion",
+ inactiveBadge: "Inaktiv",
+ addToFavorites: "Føj til favoritter",
+ removeFromFavorites: "Fjern fra favoritter",
+ noRowsLabel: "Ingen rækker",
+ noResultsOverlayLabel: "Ingen resultater fundet.",
+ footerRowSelected: (count: number) =>
+ count !== 1
+ ? `${count.toLocaleString()} rækker valgt`
+ : `${count.toLocaleString()} række valgt`,
+ footerTotalVisibleRows: (visibleCount: number, totalCount: number) =>
+ `${visibleCount.toLocaleString()} af ${totalCount.toLocaleString()}`,
+ paginationRowsPerPage: "Rækker per side:",
+ },
+};
+
+export const StringsOverrideTable = Template.bind({});
+StringsOverrideTable.args = {
+ ...baseArgs,
+ viewMode: "cells",
+ apiOverrides: {
+ data: [
+ {
+ id: "1",
+ displayName: "Bridge iTwin",
+ number: "1111-2222-3333-4444",
+ image: bridgeThumbnail,
+ status: "Trial",
+ },
+ {
+ id: "2",
+ displayName: "Power iTwin",
+ number: "2222-3333-4444-5555",
+ image: powerThumbnail,
+ status: "Inactive",
+ },
+ {
+ id: "3",
+ displayName: "Highway iTwin",
+ number: "3333-4444-5555-6666",
+ image: nightThumbnail,
+ },
+ ],
+ },
+ iTwinActions: [
+ {
+ children: "Some action",
+ key: "something",
+ onClick: (iTwin) => action("clicked " + iTwin?.displayName)(iTwin),
+ },
+ ],
+ stringsOverrides: {
+ moreOptions: "Flere muligheder",
+ trialBadge: "Prøveversion",
+ inactiveBadge: "Inaktiv",
+ addToFavorites: "Føj til favoritter",
+ removeFromFavorites: "Fjern fra favoritter",
+ tableColumnName: "iTwin Navn",
+ tableColumnDescription: "iTwin Beskrivelse",
+ tableColumnLastModified: "Sidst ændret",
+ noRowsLabel: "Ingen rækker",
+ noResultsOverlayLabel: "Ingen resultater fundet.",
+ footerRowSelected: (count: number) =>
+ count !== 1
+ ? `${count.toLocaleString()} rækker valgt`
+ : `${count.toLocaleString()} række valgt`,
+ footerTotalVisibleRows: (visibleCount: number, totalCount: number) =>
+ `${visibleCount.toLocaleString()} af ${totalCount.toLocaleString()}`,
+ paginationRowsPerPage: "Rækker per side:",
+ },
+};
+
+export default {
+ title: "imodel-browser/ITwinGridMUI",
+ component: ITwinGrid,
+ argTypes: {
+ accessToken,
+ },
+ excludeStories: ["ITwinGrid"],
+} as Meta;
diff --git a/packages/apps/storybook/src/imodel-browser/ITwinTileMUI.stories.tsx b/packages/apps/storybook/src/imodel-browser/ITwinTileMUI.stories.tsx
new file mode 100644
index 00000000..afe1574b
--- /dev/null
+++ b/packages/apps/storybook/src/imodel-browser/ITwinTileMUI.stories.tsx
@@ -0,0 +1,159 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
+ * See LICENSE.md in the project root for license terms and full copyright notice.
+ *--------------------------------------------------------------------------------------------*/
+import {
+ ITwinTile,
+ type ITwinTileProps,
+ type ITwinFull,
+} from "@itwin/imodel-browser-react/mui";
+import Box from "@mui/material/Box";
+import Chip from "@mui/material/Chip";
+import { action } from "@storybook/addon-actions";
+import { Meta, Story } from "@storybook/react/types-6-0";
+import React from "react";
+import bridgeThumbnail from "../utils/bridge.jpg";
+import powerThumbnail from "../utils/power.jpg";
+import Grid from "@mui/material/Grid";
+import svgMagnet from "@stratakit/icons/magnet.svg";
+import { DefaultThumbnail } from "../../../../modules/imodel-browser/src/containers/ITwinGrid/ITwinTileMUI";
+import { SvgThumbnail } from "@itwin/imodel-browser-react/mui";
+
+const InConstrainedContainer = ({
+ children,
+}: {
+ children: React.ReactNode;
+}) => {children};
+
+export const ITwinTileMUIStory = (props: ITwinTileProps) => (
+
+
+
+);
+
+const baseITwin: ITwinFull = {
+ id: "1",
+ displayName: "iTwin Name",
+ number: "aaaa-bbbb-cccc-dddd",
+ status: "Trial",
+ lastModifiedDateTime: "2024-01-01T12:00:00Z",
+ image: bridgeThumbnail,
+};
+
+const baseArgs: ITwinTileProps = {
+ iTwin: {
+ ...baseITwin,
+ },
+ contextMenuItems: [
+ {
+ key: "option-1",
+ children: "Option 1",
+ onClick: (iTwin) => action("iTwin option 1 clicked")(iTwin),
+ },
+ {
+ key: "option-2",
+ children: "Option 2",
+ onClick: (iTwin) => action("iTwin option 2 clicked")(iTwin),
+ },
+ ],
+ thumbnail: bridgeThumbnail,
+ onOpen: action("iTwin opened"),
+ onSelect: action("iTwin selected"),
+ addToFavorites: async (iTwinId) => {
+ action("iTwin add to favorites")(iTwinId);
+ },
+ removeFromFavorites: async (iTwinId) => {
+ action("iTwin remove from favorites")(iTwinId);
+ },
+};
+
+export default {
+ title: "imodel-browser/ITwinTileMUI",
+ component: ITwinTileMUIStory,
+ excludeStories: ["ITwinTileMUIStory"],
+ argTypes: {
+ status: {
+ options: ["undefined", "positive", "warning", "negative"],
+ mapping: {
+ undefined: undefined,
+ positive: "positive",
+ warning: "warning",
+ negative: "negative",
+ },
+ control: {
+ type: "radio",
+ },
+ },
+ iTwin: {
+ options: ["Active", "Inactive", "Trial"],
+ mapping: {
+ Active: { ...baseITwin, status: "Active", displayName: "Active iTwin" },
+ Inactive: {
+ ...baseITwin,
+ status: "Inactive",
+ displayName: "Inactive iTwin",
+ },
+ Trial: { ...baseITwin, status: "Trial", displayName: "Trial iTwin" },
+ },
+ control: {
+ type: "select",
+ },
+ },
+ contextMenuItems: { control: false },
+ onSelect: { control: false },
+ onOpen: { control: false },
+ thumbnailBottomLeft: { control: false },
+ thumbnail: { control: false },
+ actions: { control: false },
+ thumbnailTopLeft: { control: false },
+ thumbnailTopRight: { control: false },
+ children: { control: false },
+ stringsOverrides: { control: false },
+ },
+} as Meta;
+
+const Template: Story = (args) => (
+
+);
+
+export const Default = Template.bind({});
+Default.args = {
+ ...baseArgs,
+ isFavorite: false,
+ disabled: false,
+ loading: false,
+ selected: false,
+};
+
+export const DefaultThumbnailStory = Template.bind({});
+DefaultThumbnailStory.args = {
+ ...baseArgs,
+ thumbnail: ,
+};
+DefaultThumbnailStory.storyName = "Default Thumbnail";
+
+export const CustomSvgThumbnail = Template.bind({});
+CustomSvgThumbnail.args = {
+ ...baseArgs,
+ thumbnail: ,
+};
+
+export const Extensive = Template.bind({});
+Extensive.args = {
+ ...baseArgs,
+ status: "warning",
+ isFavorite: false,
+ title: "Overridden Title",
+ description: "Overriden description",
+ disabled: false,
+ loading: false,
+ selected: false,
+ thumbnailTopLeft: ,
+ thumbnail: powerThumbnail,
+ getBadge: () => ,
+ actions: [
+ { key: "open", label: "Open", onClick: action("iTwin open clicked") },
+ { key: "share", label: "Share", onClick: action("iTwin share clicked") },
+ ],
+ additionalContent: ,
+};
diff --git a/packages/apps/storybook/src/imodel-browser/NoResultsMUI.stories.tsx b/packages/apps/storybook/src/imodel-browser/NoResultsMUI.stories.tsx
new file mode 100644
index 00000000..abe95dda
--- /dev/null
+++ b/packages/apps/storybook/src/imodel-browser/NoResultsMUI.stories.tsx
@@ -0,0 +1,40 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
+ * See LICENSE.md in the project root for license terms and full copyright notice.
+ *--------------------------------------------------------------------------------------------*/
+import {
+ NoResults as ExternalComponent,
+ type NoResultsProps as NoResultsMUIProps,
+} from "@itwin/imodel-browser-react/mui";
+import { Meta, Story } from "@storybook/react/types-6-0";
+import React from "react";
+
+export const NoResults = (props: NoResultsMUIProps) => (
+
+);
+
+export default {
+ title: "imodel-browser/NoResultsMUI",
+ component: NoResults,
+ excludeStories: ["NoResults"],
+} as Meta;
+
+const Template: Story = (args) => ;
+
+export const Primary = Template.bind({});
+Primary.args = {
+ text: "No iModels available",
+};
+
+export const SearchResults = Template.bind({});
+SearchResults.args = {
+ text: "No search results",
+ subtext: "Try adjusting your search criteria.",
+ isSearchResult: true,
+};
+
+export const WithSubtext = Template.bind({});
+WithSubtext.args = {
+ text: "No iModels available",
+ subtext: "Please check back later.",
+};
diff --git a/packages/apps/storybook/src/stratakit.d.ts b/packages/apps/storybook/src/stratakit.d.ts
new file mode 100644
index 00000000..1d70ac44
--- /dev/null
+++ b/packages/apps/storybook/src/stratakit.d.ts
@@ -0,0 +1 @@
+///
diff --git a/packages/apps/storybook/src/utils/README.md b/packages/apps/storybook/src/utils/README.md
new file mode 100644
index 00000000..fda49d5b
--- /dev/null
+++ b/packages/apps/storybook/src/utils/README.md
@@ -0,0 +1,8 @@
+Placeholder images are royalty-free from Unsplash
+
+"Free to use under the Unsplash License"
+
+https://unsplash.com/photos/time-lapse-photo-of-concrete-highway-with-cars-vdBE638sszE
+https://unsplash.com/photos/golden-gate-bridge-san-francisco-california-vj_9l20fzj0
+https://unsplash.com/photos/high-angle-photo-of-road-with-vehicles-NSuufgf-BME
+https://unsplash.com/photos/birds-eye-photography-of-concrete-structure-bv2pvCGMtzg
diff --git a/packages/apps/storybook/src/utils/bridge.jpg b/packages/apps/storybook/src/utils/bridge.jpg
new file mode 100644
index 00000000..4b88857d
Binary files /dev/null and b/packages/apps/storybook/src/utils/bridge.jpg differ
diff --git a/packages/apps/storybook/src/utils/night.jpg b/packages/apps/storybook/src/utils/night.jpg
new file mode 100644
index 00000000..337af120
Binary files /dev/null and b/packages/apps/storybook/src/utils/night.jpg differ
diff --git a/packages/apps/storybook/src/utils/overpass.jpg b/packages/apps/storybook/src/utils/overpass.jpg
new file mode 100644
index 00000000..9a88592c
Binary files /dev/null and b/packages/apps/storybook/src/utils/overpass.jpg differ
diff --git a/packages/apps/storybook/src/utils/power.jpg b/packages/apps/storybook/src/utils/power.jpg
new file mode 100644
index 00000000..8ee6b967
Binary files /dev/null and b/packages/apps/storybook/src/utils/power.jpg differ
diff --git a/packages/apps/storybook/src/utils/storyHelp.ts b/packages/apps/storybook/src/utils/storyHelp.ts
index 47847604..61815021 100644
--- a/packages/apps/storybook/src/utils/storyHelp.ts
+++ b/packages/apps/storybook/src/utils/storyHelp.ts
@@ -14,7 +14,7 @@ export const accessTokenArgTypes = {
/** HOC that will override the "accessToken" prop with the Addon token */
export const withAccessTokenOverride: <
- T extends { accessToken?: string | (() => Promise) }
+ T extends { accessToken?: string | (() => Promise) },
>(
story: Story
) => Story = (Story) => (args, context) =>
@@ -24,4 +24,13 @@ export const withAccessTokenOverride: <
export const withITwinIdOverride: (
story: Story
) => Story = (Story) => (args, context) =>
- Story({ ...args, iTwinId: args.iTwinId ?? context.globals.iTwinId }, context);
+ Story(
+ {
+ ...args,
+ iTwinId:
+ args.iTwinId ??
+ context.globals.iTwinId ??
+ "23a67b97-30b3-4cdb-82c0-752edd10606b",
+ },
+ context
+ );
diff --git a/packages/apps/storybook/tsconfig.eslint.json b/packages/apps/storybook/tsconfig.eslint.json
index 1e483cfb..06164757 100644
--- a/packages/apps/storybook/tsconfig.eslint.json
+++ b/packages/apps/storybook/tsconfig.eslint.json
@@ -1,10 +1,11 @@
{
"compilerOptions": {
- "strictNullChecks": true
+ "strictNullChecks": true,
+ "paths": {
+ "@itwin/imodel-browser-react/mui": [
+ "../../modules/imodel-browser/src/mui/index.ts"
+ ]
+ }
},
- "include": [
- "**/*.ts*",
- "**/*.js*",
- "**/.*.js*"
- ],
-}
\ No newline at end of file
+ "include": ["**/*.ts*", "**/*.js*", "**/.*.js*"]
+}
diff --git a/packages/apps/storybook/tsconfig.json b/packages/apps/storybook/tsconfig.json
new file mode 100644
index 00000000..67577c3c
--- /dev/null
+++ b/packages/apps/storybook/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "strictNullChecks": true,
+ "paths": {
+ "@itwin/imodel-browser-react/mui": [
+ "../../modules/imodel-browser/src/mui/index.ts"
+ ]
+ }
+ },
+ "include": [
+ "**/*.ts*",
+ "**/*.js*",
+ "**/.*.js*",
+ "../../modules/imodel-browser/src/typings/declarations.d.ts"
+ ]
+}
diff --git a/packages/modules/imodel-browser/MUI-MIGRATION.md b/packages/modules/imodel-browser/MUI-MIGRATION.md
new file mode 100644
index 00000000..f1a058a9
--- /dev/null
+++ b/packages/modules/imodel-browser/MUI-MIGRATION.md
@@ -0,0 +1,408 @@
+# MUI Migration Notes
+
+This file tracks migration notes for the MUI/Stratakit components.
+
+A new `src/mui/index.ts` barrel re-exports MUI components under legacy-aligned names (e.g. `IModelGridMUI as IModelGrid`). This is built as a separate rollup entry point.
+
+## Styling approach
+
+All MUI components use **inline `sx` props** instead of CSS module (`.module.scss`) files. This avoids injecting `