diff --git a/jsapp/js/components/common/modal.stories.tsx b/jsapp/js/components/common/modal.stories.tsx
new file mode 100644
index 0000000000..4b78c8c5e4
--- /dev/null
+++ b/jsapp/js/components/common/modal.stories.tsx
@@ -0,0 +1,83 @@
+import type {ModalProps} from '@mantine/core';
+import {Button, Center, Modal, Stack, Text, Group} from '@mantine/core';
+import type {Meta, StoryObj} from '@storybook/react';
+import {useArgs} from '@storybook/preview-api';
+import {useDisclosure} from '@mantine/hooks';
+
+const RenderModal = ({...args}: ModalProps) => {
+ const [{opened}, updateArgs] = useArgs();
+
+ return (
+
+
+ updateArgs({opened: !opened})}>
+
+
+ Example modal content. Press esc, click outside or close button to
+ close.
+
+
+
+
+
+
+
+
+ );
+};
+
+/**
+ * Mantine [Modal](https://mantine.dev/core/modal/) component stories.
+ */
+const meta: Meta = {
+ title: 'Common/Modal',
+ component: Modal,
+ render: RenderModal,
+ argTypes: {
+ opened: {
+ description: 'Modal opened state',
+ type: 'boolean',
+ },
+ size: {
+ description: 'Modal size',
+ type: 'string',
+ control: {
+ type: 'select',
+ },
+ options: ['auto', 'xs', 'sm', 'md', 'lg', 'xl', '50%', '75%', '100%'],
+ },
+ fullScreen: {
+ description: 'Modal fullscreen state',
+ type: 'boolean',
+ },
+ centered: {
+ description: 'Center modal content',
+ type: 'boolean',
+ },
+ withCloseButton: {
+ description: 'Render close button',
+ type: 'boolean',
+ },
+ title: {
+ description: 'Modal title',
+ type: 'string',
+ },
+ },
+ args: {
+ opened: false,
+ closeOnClickOutside: true,
+ withCloseButton: true,
+ title: 'Modal title',
+ centered: false,
+ size: 'md',
+ fullScreen: false,
+ },
+};
+
+type Story = StoryObj;
+
+export const Basic: Story = {
+ args: {},
+};
+
+export default meta;
diff --git a/jsapp/js/theme/kobo/Modal.module.css b/jsapp/js/theme/kobo/Modal.module.css
new file mode 100644
index 0000000000..78c69ff10e
--- /dev/null
+++ b/jsapp/js/theme/kobo/Modal.module.css
@@ -0,0 +1,15 @@
+.close {
+ color: var(--mantine-color-gray-4);
+ background-color: transparent;
+
+ &:hover {
+ color: var(--mantine-color-gray-2);
+ background-color: transparent;
+ }
+}
+
+.title {
+ font-size: var(--mantine-font-size-xl);
+ font-weight: var(--mantine-heading-font-weight);
+ color: var(--mantine-color-gray-0);
+}
diff --git a/jsapp/js/theme/kobo/Modal.tsx b/jsapp/js/theme/kobo/Modal.tsx
new file mode 100644
index 0000000000..73d31ccf51
--- /dev/null
+++ b/jsapp/js/theme/kobo/Modal.tsx
@@ -0,0 +1,18 @@
+import {Modal} from '@mantine/core';
+import Icon from 'jsapp/js/components/common/icon';
+import classes from './Modal.module.css';
+
+export const ModalThemeKobo = Modal.extend({
+ defaultProps: {
+ closeButtonProps: {
+ icon: ,
+ },
+ overlayProps: {
+ backgroundOpacity: 0.5,
+ color: 'var(--mantine-color-blue-9)',
+ zIndex: 3000,
+ },
+ zIndex: 4000,
+ },
+ classNames: classes,
+});
diff --git a/jsapp/js/theme/kobo/index.ts b/jsapp/js/theme/kobo/index.ts
index e897baaf68..90092ea3f8 100644
--- a/jsapp/js/theme/kobo/index.ts
+++ b/jsapp/js/theme/kobo/index.ts
@@ -7,6 +7,7 @@ import {MenuThemeKobo} from './Menu';
import {AlertThemeKobo} from './Alert';
import {SelectThemeKobo} from './Select';
import {LoaderThemeKobo} from './Loader';
+import {ModalThemeKobo} from './Modal';
export const themeKobo = createTheme({
primaryColor: 'blue',
@@ -110,5 +111,6 @@ export const themeKobo = createTheme({
Table: TableThemeKobo,
Select: SelectThemeKobo,
Loader: LoaderThemeKobo,
+ Modal: ModalThemeKobo,
},
});