@linagora/twake-mui 15.2.0 → 16.0.1

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 (64) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/components/ContactPopover/ContactPopover.stories.d.ts +1 -0
  3. package/dist/components/ContactPopover/ContactPopover.stories.js +25 -30
  4. package/dist/components/ContactPopover/ContactPopoverCalendarAction.js +5 -2
  5. package/dist/components/ContactPopover/ContactPopoverChatAction.js +5 -2
  6. package/dist/components/ContactPopover/ContactPopoverVideoAction.js +5 -2
  7. package/dist/components/Dialog/Dialog.stories.d.ts +1 -1
  8. package/dist/components/Dialog/Dialog.stories.js +1 -1
  9. package/dist/components/Dialog/index.d.ts +1 -1
  10. package/dist/components/Dialog/index.js +1 -1
  11. package/dist/components/Dialogs/ConfirmDialog.d.ts +8 -0
  12. package/dist/components/Dialogs/ConfirmDialog.js +16 -0
  13. package/dist/components/Dialogs/DialogBackButton.d.ts +8 -0
  14. package/dist/components/Dialogs/DialogBackButton.js +14 -0
  15. package/dist/components/Dialogs/DialogCloseButton.d.ts +8 -0
  16. package/dist/components/Dialogs/DialogCloseButton.js +14 -0
  17. package/dist/components/Dialogs/DialogFluidContent.d.ts +12 -0
  18. package/dist/components/Dialogs/DialogFluidContent.js +11 -0
  19. package/dist/components/Dialogs/DialogNavButtons.d.ts +9 -0
  20. package/dist/components/Dialogs/DialogNavButtons.js +14 -0
  21. package/dist/components/Dialogs/Dialogs.stories.d.ts +33 -0
  22. package/dist/components/Dialogs/Dialogs.stories.js +177 -0
  23. package/dist/components/Dialogs/FixedActionsDialog.d.ts +8 -0
  24. package/dist/components/Dialogs/FixedActionsDialog.js +20 -0
  25. package/dist/components/Dialogs/FixedDialog.d.ts +8 -0
  26. package/dist/components/Dialogs/FixedDialog.js +20 -0
  27. package/dist/components/Dialogs/IllustrationDialog.d.ts +8 -0
  28. package/dist/components/Dialogs/IllustrationDialog.js +21 -0
  29. package/dist/components/Dialogs/InstallFlagshipAppDialog.d.ts +13 -0
  30. package/dist/components/Dialogs/InstallFlagshipAppDialog.js +26 -0
  31. package/dist/components/Dialogs/PermissionDialog.d.ts +11 -0
  32. package/dist/components/Dialogs/PermissionDialog.js +15 -0
  33. package/dist/components/Dialogs/PermissionDialogIcon.d.ts +3 -0
  34. package/dist/components/Dialogs/PermissionDialogIcon.js +8 -0
  35. package/dist/components/Dialogs/ShortcutDialog.d.ts +15 -0
  36. package/dist/components/Dialogs/ShortcutDialog.js +41 -0
  37. package/dist/components/Dialogs/SlideUpTransition.d.ts +5 -0
  38. package/dist/components/Dialogs/SlideUpTransition.js +6 -0
  39. package/dist/components/Dialogs/SpecificDialogs.stories.d.ts +7 -0
  40. package/dist/components/Dialogs/SpecificDialogs.stories.js +43 -0
  41. package/dist/components/Dialogs/StandardDialog.d.ts +11 -0
  42. package/dist/components/Dialogs/StandardDialog.js +21 -0
  43. package/dist/components/Dialogs/images/appStore.png +0 -0
  44. package/dist/components/Dialogs/images/installFlagshipAppQrCode.png +0 -0
  45. package/dist/components/Dialogs/images/playStore.png +0 -0
  46. package/dist/components/Dialogs/locales/de.json +25 -0
  47. package/dist/components/Dialogs/locales/en.json +25 -0
  48. package/dist/components/Dialogs/locales/es.json +25 -0
  49. package/dist/components/Dialogs/locales/fr.json +25 -0
  50. package/dist/components/Dialogs/locales/index.d.ts +177 -0
  51. package/dist/components/Dialogs/locales/index.js +8 -0
  52. package/dist/components/Dialogs/locales/it.json +25 -0
  53. package/dist/components/Dialogs/locales/ru.json +25 -0
  54. package/dist/components/Dialogs/locales/vi.json +25 -0
  55. package/dist/components/Dialogs/shortcutHelpers.d.ts +19 -0
  56. package/dist/components/Dialogs/shortcutHelpers.js +49 -0
  57. package/dist/components/Dialogs/types.d.ts +7 -0
  58. package/dist/components/Dialogs/types.js +1 -0
  59. package/dist/components/Dialogs/useDialogLayout.d.ts +40 -0
  60. package/dist/components/Dialogs/useDialogLayout.js +63 -0
  61. package/dist/index.d.ts +24 -1
  62. package/dist/index.js +12 -1
  63. package/dist/lib/overrides.js +71 -6
  64. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,3 +1,35 @@
1
+ ## @linagora/twake-mui [16.0.1](https://github.com/linagora/twake-ui/compare/@linagora/twake-mui@16.0.0...@linagora/twake-mui@16.0.1) (2026-10-19)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **twake-mui:** Disabled ContactPopoved action has now visible state ([bcbf81e](https://github.com/linagora/twake-ui/commit/bcbf81e29cb997599cacc5fb0ea34a5813905820))
7
+
8
+ # @linagora/twake-mui [16.0.0](https://github.com/linagora/twake-ui/compare/@linagora/twake-mui@15.2.0...@linagora/twake-mui@16.0.0) (2026-10-19)
9
+
10
+
11
+ ### Code Refactoring
12
+
13
+ * **twake-mui:** Export Dialog as default only ([ed447eb](https://github.com/linagora/twake-ui/commit/ed447eb8041974c1b4d3b55bb7160740003a787e))
14
+
15
+
16
+ ### Features
17
+
18
+ * **twake-mui:** Migrate ConfirmDialog from cozy-ui ([62098f2](https://github.com/linagora/twake-ui/commit/62098f26bafbf660126e9a408fbb650d3914187b))
19
+ * **twake-mui:** Migrate FixedActionsDialog from cozy-ui ([61c34b1](https://github.com/linagora/twake-ui/commit/61c34b12069ab66d59498342790a26508f00d8a2))
20
+ * **twake-mui:** Migrate FixedDialog from cozy-ui ([bb6ba7f](https://github.com/linagora/twake-ui/commit/bb6ba7f66938fff8bd8cc51ab9c17c7554119394))
21
+ * **twake-mui:** Migrate IllustrationDialog from cozy-ui ([ba5525e](https://github.com/linagora/twake-ui/commit/ba5525ef40e9ec4d018c62ef96f9d6d8806c17fa))
22
+ * **twake-mui:** Migrate InstallFlagshipAppDialog from cozy-ui ([4869572](https://github.com/linagora/twake-ui/commit/48695729c9c89651d21f10140fa3fd83c6e2b1d4))
23
+ * **twake-mui:** Migrate PermissionDialog from cozy-ui ([b8f9628](https://github.com/linagora/twake-ui/commit/b8f962838b91c23b85d8528b07504be79d46f33f))
24
+ * **twake-mui:** Migrate ShortcutDialog from cozy-ui ([74c0614](https://github.com/linagora/twake-ui/commit/74c061468784736d77ef3a738b20ba4d0bfece15))
25
+ * **twake-mui:** Migrate StandardDialog from cozy-ui ([a43530f](https://github.com/linagora/twake-ui/commit/a43530f7906a39a518ae97a15b0e006d1e46ab71))
26
+
27
+
28
+ ### BREAKING CHANGES
29
+
30
+ * **twake-mui:** DialogDefault is no longer exported. Import Dialog
31
+ instead.
32
+
1
33
  # @linagora/twake-mui [15.2.0](https://github.com/linagora/twake-ui/compare/@linagora/twake-mui@15.1.0...@linagora/twake-mui@15.2.0) (2026-10-19)
2
34
 
3
35
 
@@ -4,3 +4,4 @@ declare const meta: Meta<typeof ContactPopover>;
4
4
  export default meta;
5
5
  type Story = StoryObj<typeof ContactPopover>;
6
6
  export declare const Default: Story;
7
+ export declare const AllActionsVariants: Story;
@@ -31,39 +31,34 @@ const meta = {
31
31
  }
32
32
  };
33
33
  export default meta;
34
+ const Variant = ({ title, children }) => (React.createElement(Box, null,
35
+ React.createElement(Typography, { variant: "subtitle2", gutterBottom: true }, title),
36
+ children));
34
37
  export const Default = {
35
- render: () => {
36
- return (React.createElement(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 3 } },
37
- React.createElement(Box, null,
38
- React.createElement(Typography, { variant: "subtitle2", gutterBottom: true }, "Default \u2014 all actions"),
39
- React.createElement(ContactPopover, { name: "John Doe", email: "john.doe@example.com" },
40
- React.createElement(Chip, { label: "John Doe", clickable: true }),
41
- React.createElement(ContactPopover.Actions, null,
42
- React.createElement(ContactPopover.EmailAction, { url: "https://mail.example.com/john.doe" }),
43
- React.createElement(ContactPopover.CalendarAction, { url: "https://calendar.example.com/john.doe" }),
44
- React.createElement(ContactPopover.ChatAction, { url: "https://chat.example.com/john.doe" }),
45
- React.createElement(ContactPopover.VideoAction, { url: "https://meet.example.com/john.doe" })))),
46
- React.createElement(Box, null,
47
- React.createElement(Typography, { variant: "subtitle2", gutterBottom: true }, "Email only"),
48
- React.createElement(ContactPopover, { name: "", email: "jane.smith@example.com" },
49
- React.createElement(Chip, { label: "jane.smith@example.com", clickable: true }),
50
- React.createElement(ContactPopover.Actions, null,
51
- React.createElement(ContactPopover.EmailAction, { url: "mailto:jane.smith@example.com" })))),
52
- React.createElement(Box, null,
53
- React.createElement(Typography, { variant: "subtitle2", gutterBottom: true }, "Without actions"),
54
- React.createElement(ContactPopover, { name: "Alice Brown", email: "alice.brown@example.com" },
55
- React.createElement(Chip, { label: "Alice Brown", clickable: true }))),
56
- React.createElement(Box, null,
57
- React.createElement(Typography, { variant: "subtitle2", gutterBottom: true }, "Long name / email"),
58
- React.createElement(ContactPopover, { name: "Christopher Alexander Montgomery", email: "christopher.montgomery@verylongdomain.example.com" },
59
- React.createElement(Chip, { label: "Christopher Alexander Montgomery", clickable: true }),
60
- React.createElement(ContactPopover.Actions, null,
61
- React.createElement(ContactPopover.ChatAction, { url: "https://chat.example.com/christopher" }),
62
- React.createElement(ContactPopover.VideoAction, { url: "https://meet.example.com/christopher" }),
63
- React.createElement(ContactPopover.CalendarAction, { url: "https://calendar.example.com/christopher" }))))));
64
- },
38
+ render: () => (React.createElement(ContactPopover, { name: "John Doe", email: "john.doe@example.com" },
39
+ React.createElement(Chip, { label: "John Doe", clickable: true }),
40
+ React.createElement(ContactPopover.Actions, null,
41
+ React.createElement(ContactPopover.EmailAction, { url: "https://mail.example.com/john.doe" }),
42
+ React.createElement(ContactPopover.CalendarAction, { url: "https://calendar.example.com/john.doe" }),
43
+ React.createElement(ContactPopover.ChatAction, { url: "https://chat.example.com/john.doe" }),
44
+ React.createElement(ContactPopover.VideoAction, { url: "https://meet.example.com/john.doe" })))),
65
45
  play: async ({ canvas, userEvent }) => {
66
46
  await userEvent.click(canvas.getByText('John Doe'));
67
47
  await screen.findByText('john.doe@example.com');
68
48
  }
69
49
  };
50
+ export const AllActionsVariants = {
51
+ render: () => (React.createElement(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 3 } },
52
+ React.createElement(Variant, { title: "Default \u2014 all actions" },
53
+ React.createElement(ContactPopover.Actions, null,
54
+ React.createElement(ContactPopover.EmailAction, { url: "https://mail.example.com/john.doe" }),
55
+ React.createElement(ContactPopover.CalendarAction, { url: "https://calendar.example.com/john.doe" }),
56
+ React.createElement(ContactPopover.ChatAction, { url: "https://chat.example.com/john.doe" }),
57
+ React.createElement(ContactPopover.VideoAction, { url: "https://meet.example.com/john.doe" }))),
58
+ React.createElement(Variant, { title: "Default \u2014 all actions \u2014 Disabled" },
59
+ React.createElement(ContactPopover.Actions, null,
60
+ React.createElement(ContactPopover.EmailAction, { disabled: true, url: "https://mail.example.com/jane.doe" }),
61
+ React.createElement(ContactPopover.CalendarAction, { disabled: true, url: "https://calendar.example.com/jane.doe" }),
62
+ React.createElement(ContactPopover.ChatAction, { disabled: true, url: "https://chat.example.com/jane.doe" }),
63
+ React.createElement(ContactPopover.VideoAction, { disabled: true, url: "https://meet.example.com/jane.doe" })))))
64
+ };
@@ -4,6 +4,9 @@ import { IconButton, useTheme } from '@mui/material';
4
4
  import React from 'react';
5
5
  export const ContactPopoverCalendarAction = ({ url, disabled = false }) => {
6
6
  const theme = useTheme();
7
- return (React.createElement(IconButton, { href: url, target: "_blank", rel: "noopener noreferrer", size: "small", disabled: disabled, sx: { border: `1px solid ${theme.vars.palette.divider}` } },
8
- React.createElement(Icon, { icon: CalendarToday, size: 20, color: theme.vars.palette.text.icon })));
7
+ return (React.createElement(IconButton, { href: url, target: "_blank", rel: "noopener noreferrer", size: "small", disabled: disabled, sx: {
8
+ border: `1px solid ${theme.vars.palette.divider}`,
9
+ color: theme.vars.palette.text.icon
10
+ } },
11
+ React.createElement(Icon, { icon: CalendarToday, size: 20 })));
9
12
  };
@@ -4,6 +4,9 @@ import { IconButton, useTheme } from '@mui/material';
4
4
  import React from 'react';
5
5
  export const ContactPopoverChatAction = ({ url, disabled = false }) => {
6
6
  const theme = useTheme();
7
- return (React.createElement(IconButton, { href: url, target: "_blank", rel: "noopener noreferrer", size: "small", disabled: disabled, sx: { border: `1px solid ${theme.vars.palette.divider}` } },
8
- React.createElement(Icon, { icon: Discuss, size: 20, color: theme.vars.palette.text.icon })));
7
+ return (React.createElement(IconButton, { href: url, target: "_blank", rel: "noopener noreferrer", size: "small", disabled: disabled, sx: {
8
+ border: `1px solid ${theme.vars.palette.divider}`,
9
+ color: theme.vars.palette.text.icon
10
+ } },
11
+ React.createElement(Icon, { icon: Discuss, size: 20 })));
9
12
  };
@@ -4,6 +4,9 @@ import { IconButton, useTheme } from '@mui/material';
4
4
  import React from 'react';
5
5
  export const ContactPopoverVideoAction = ({ url, disabled = false }) => {
6
6
  const theme = useTheme();
7
- return (React.createElement(IconButton, { href: url, target: "_blank", rel: "noopener noreferrer", size: "small", disabled: disabled, sx: { border: `1px solid ${theme.vars.palette.divider}` } },
8
- React.createElement(Icon, { icon: Camera, size: 20, color: theme.vars.palette.text.icon })));
7
+ return (React.createElement(IconButton, { href: url, target: "_blank", rel: "noopener noreferrer", size: "small", disabled: disabled, sx: {
8
+ border: `1px solid ${theme.vars.palette.divider}`,
9
+ color: theme.vars.palette.text.icon
10
+ } },
11
+ React.createElement(Icon, { icon: Camera, size: 20 })));
9
12
  };
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { Dialog } from './index';
2
+ import Dialog from './index';
3
3
  declare const meta: Meta<typeof Dialog>;
4
4
  export default meta;
5
5
  type Story = StoryObj<typeof meta>;
@@ -1,7 +1,7 @@
1
1
  import { CrossMedium, FileTypeFolder, FileTypeText, Icon } from '@linagora/twake-icons';
2
2
  import { Button, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, IconButton, List, ListItemIcon, Stack } from '@mui/material';
3
3
  import React from 'react';
4
- import { Dialog } from './index';
4
+ import Dialog from './index';
5
5
  import ListItemButton from '../ListItemButton';
6
6
  import ListItemText from '../ListItemText';
7
7
  const sizes = ['small', 'medium', 'large', 'full'];
@@ -5,5 +5,5 @@ export interface DialogProps extends MuiDialogProps {
5
5
  /** Paper width. Every size but `small` goes full screen on mobile */
6
6
  size?: DialogSize;
7
7
  }
8
- export declare const Dialog: React.FC<DialogProps>;
8
+ declare const Dialog: React.FC<DialogProps>;
9
9
  export default Dialog;
@@ -2,7 +2,7 @@ import { Dialog as MuiDialog } from '@mui/material';
2
2
  import cx from 'classnames';
3
3
  import React from 'react';
4
4
  import { useBreakpoints } from '../../hooks/useBreakpoints';
5
- export const Dialog = ({ size = 'medium', fullScreen, classes, ...props }) => {
5
+ const Dialog = ({ size = 'medium', fullScreen, classes, ...props }) => {
6
6
  const { isMobile } = useBreakpoints();
7
7
  return (React.createElement(MuiDialog, { fullScreen: fullScreen ?? (size !== 'small' && isMobile), classes: { ...classes, paper: cx(size, classes?.paper) }, ...props }));
8
8
  };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { DialogSectionsProps } from './types';
3
+ import { DialogLayoutProps } from './useDialogLayout';
4
+ export interface ConfirmDialogProps extends DialogLayoutProps, DialogSectionsProps {
5
+ }
6
+ /** Small dialog asking to confirm an action, its title scrolling with the content */
7
+ declare const ConfirmDialog: ({ size, title, content, actions, actionsLayout, ...props }: ConfirmDialogProps) => React.ReactElement;
8
+ export default ConfirmDialog;
@@ -0,0 +1,16 @@
1
+ import { DialogContent, DialogTitle } from '@mui/material';
2
+ import React from 'react';
3
+ import DialogFluidContent from './DialogFluidContent';
4
+ import DialogNavButtons from './DialogNavButtons';
5
+ import { useDialogLayout } from './useDialogLayout';
6
+ import Dialog from '../Dialog';
7
+ /** Small dialog asking to confirm an action, its title scrolling with the content */
8
+ const ConfirmDialog = ({ size = 'small', title, content, actions, actionsLayout, ...props }) => {
9
+ const { id, fullScreen, dialogProps, dialogTitleProps, dialogActionsProps, dialogContentProps } = useDialogLayout({ ...props, size, isFluidTitle: true });
10
+ return (React.createElement(Dialog, { ...dialogProps },
11
+ React.createElement(DialogNavButtons, { id: id, fullScreen: fullScreen, onClose: props.onClose, onBack: props.onBack }),
12
+ React.createElement(DialogContent, { ...dialogContentProps },
13
+ React.createElement(DialogFluidContent, { header: title && (React.createElement("div", { className: "dialogTitleFluidContainer" },
14
+ React.createElement(DialogTitle, { ...dialogTitleProps }, title))), content: content, actions: actions, actionsLayout: actionsLayout, dialogActionsProps: dialogActionsProps }))));
15
+ };
16
+ export default ConfirmDialog;
@@ -0,0 +1,8 @@
1
+ import { IconButtonProps } from '@mui/material';
2
+ import React from 'react';
3
+ export interface DialogBackButtonProps extends IconButtonProps {
4
+ onClick: NonNullable<IconButtonProps['onClick']>;
5
+ }
6
+ /** Back button pinned to the top left corner of a dialog */
7
+ declare const DialogBackButton: ({ className, ...props }: DialogBackButtonProps) => React.ReactElement;
8
+ export default DialogBackButton;
@@ -0,0 +1,14 @@
1
+ import { Icon, Previous } from '@linagora/twake-icons';
2
+ import { IconButton } from '@mui/material';
3
+ import cx from 'classnames';
4
+ import React from 'react';
5
+ import { useExtendI18n, useI18n } from 'twake-i18n';
6
+ import { locales } from './locales';
7
+ /** Back button pinned to the top left corner of a dialog */
8
+ const DialogBackButton = ({ className, ...props }) => {
9
+ useExtendI18n(locales);
10
+ const { t } = useI18n();
11
+ return (React.createElement(IconButton, { ...props, "aria-label": t('Dialogs.backButton'), className: cx('dialogIconButton', 'dialogBackButton', className), size: "medium" },
12
+ React.createElement(Icon, { icon: Previous })));
13
+ };
14
+ export default DialogBackButton;
@@ -0,0 +1,8 @@
1
+ import { IconButtonProps } from '@mui/material';
2
+ import React from 'react';
3
+ export interface DialogCloseButtonProps extends IconButtonProps {
4
+ onClick: NonNullable<IconButtonProps['onClick']>;
5
+ }
6
+ /** Close button pinned to the top right corner of a dialog */
7
+ declare const DialogCloseButton: ({ className, ...props }: DialogCloseButtonProps) => React.ReactElement;
8
+ export default DialogCloseButton;
@@ -0,0 +1,14 @@
1
+ import { CrossMedium, Icon } from '@linagora/twake-icons';
2
+ import { IconButton } from '@mui/material';
3
+ import cx from 'classnames';
4
+ import React from 'react';
5
+ import { useExtendI18n, useI18n } from 'twake-i18n';
6
+ import { locales } from './locales';
7
+ /** Close button pinned to the top right corner of a dialog */
8
+ const DialogCloseButton = ({ className, ...props }) => {
9
+ useExtendI18n(locales);
10
+ const { t } = useI18n();
11
+ return (React.createElement(IconButton, { ...props, "aria-label": t('Dialogs.closeButton'), className: cx('dialogIconButton', 'dialogCloseButton', className), size: "medium" },
12
+ React.createElement(Icon, { icon: CrossMedium })));
13
+ };
14
+ export default DialogCloseButton;
@@ -0,0 +1,12 @@
1
+ import { DialogActionsProps } from '@mui/material';
2
+ import React from 'react';
3
+ import type { DialogSectionsProps } from './types';
4
+ export interface DialogFluidContentProps extends Omit<DialogSectionsProps, 'title'> {
5
+ /** Rendered above the content, inside the scrolling area */
6
+ header?: React.ReactNode;
7
+ dialogActionsProps: DialogActionsProps;
8
+ className?: string;
9
+ }
10
+ /** Content of a dialog whose actions follow the content, and stick to the bottom of the screen on mobile */
11
+ declare const DialogFluidContent: ({ header, content, actions, actionsLayout, dialogActionsProps, className }: DialogFluidContentProps) => React.ReactElement;
12
+ export default DialogFluidContent;
@@ -0,0 +1,11 @@
1
+ import { DialogActions } from '@mui/material';
2
+ import cx from 'classnames';
3
+ import React from 'react';
4
+ /** Content of a dialog whose actions follow the content, and stick to the bottom of the screen on mobile */
5
+ const DialogFluidContent = ({ header, content, actions, actionsLayout, dialogActionsProps, className }) => (React.createElement("div", { className: cx('dialogContentInner withFluidActions', className) },
6
+ header,
7
+ React.createElement("div", { className: cx('dialogContentWrapper', {
8
+ withActions: Boolean(actions)
9
+ }) }, content),
10
+ actions && (React.createElement(DialogActions, { ...dialogActionsProps, disableSpacing: true, className: cx('dialogActionsFluid', { columnLayout: actionsLayout === 'column' }, dialogActionsProps.className) }, actions))));
11
+ export default DialogFluidContent;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { DialogLayoutProps } from './useDialogLayout';
3
+ export interface DialogNavButtonsProps extends Pick<DialogLayoutProps, 'onClose' | 'onBack'> {
4
+ id: string;
5
+ fullScreen: boolean;
6
+ }
7
+ /** Close and back buttons of a dialog. On full screen, a single back button calls `onBack`, or `onClose` without it */
8
+ declare const DialogNavButtons: ({ id, fullScreen, onClose, onBack }: DialogNavButtonsProps) => React.ReactElement | null;
9
+ export default DialogNavButtons;
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import DialogBackButton from './DialogBackButton';
3
+ import DialogCloseButton from './DialogCloseButton';
4
+ /** Close and back buttons of a dialog. On full screen, a single back button calls `onBack`, or `onClose` without it */
5
+ const DialogNavButtons = ({ id, fullScreen, onClose, onBack }) => {
6
+ const onBackOrClose = onBack ?? onClose;
7
+ if (fullScreen) {
8
+ return onBackOrClose ? (React.createElement(DialogBackButton, { onClick: onBackOrClose, "data-testid": `modal-backclose-button-${id}` })) : null;
9
+ }
10
+ return (React.createElement(React.Fragment, null,
11
+ onClose && (React.createElement(DialogCloseButton, { onClick: onClose, "data-testid": `modal-close-button-${id}` })),
12
+ onBack && (React.createElement(DialogBackButton, { onClick: onBack, "data-testid": `modal-back-button-${id}` }))));
13
+ };
14
+ export default DialogNavButtons;
@@ -0,0 +1,33 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import React from 'react';
3
+ import { StandardDialogProps } from './StandardDialog';
4
+ declare const dialogs: {
5
+ StandardDialog: ({ title, content, actions, actionsLayout, ...props }: StandardDialogProps) => React.ReactElement;
6
+ ConfirmDialog: ({ size, title, content, actions, actionsLayout, ...props }: import("./ConfirmDialog").ConfirmDialogProps) => React.ReactElement;
7
+ FixedDialog: ({ title, content, actions, actionsLayout, ...props }: import("./FixedDialog").FixedDialogProps) => React.ReactElement;
8
+ FixedActionsDialog: ({ title, content, actions, actionsLayout, ...props }: import("./FixedActionsDialog").FixedActionsDialogProps) => React.ReactElement;
9
+ IllustrationDialog: ({ title, content, actions, actionsLayout, ...props }: import("./IllustrationDialog").IllustrationDialogProps) => React.ReactElement;
10
+ PermissionDialog: React.FC<StandardDialogProps>;
11
+ };
12
+ type DialogName = keyof typeof dialogs;
13
+ interface StoryArgs extends Pick<StandardDialogProps, 'size' | 'actionsLayout' | 'align' | 'disableTitleAutoPadding' | 'disableGutters' | 'fullScreen'> {
14
+ dialog: DialogName;
15
+ contentLength: 'short' | 'long';
16
+ longTitle: boolean;
17
+ hideTitle: boolean;
18
+ showActions: boolean;
19
+ withCloseButton: boolean;
20
+ withBackButton: boolean;
21
+ withBackground: boolean;
22
+ titleWithButton: boolean;
23
+ }
24
+ declare const meta: Meta<StoryArgs>;
25
+ export default meta;
26
+ type Story = StoryObj<typeof meta>;
27
+ export declare const Default: Story;
28
+ export declare const StandardDialogScreenshot: Story;
29
+ export declare const ConfirmDialogScreenshot: Story;
30
+ export declare const FixedDialogScreenshot: Story;
31
+ export declare const FixedActionsDialogScreenshot: Story;
32
+ export declare const IllustrationDialogScreenshot: Story;
33
+ export declare const PermissionDialogScreenshot: Story;
@@ -0,0 +1,177 @@
1
+ import { Attachment, Cloud, Dots, Icon, Pen, People } from '@linagora/twake-icons';
2
+ import { Button, IconButton, ListItemIcon, Menu, MenuItem, Stack, Typography } from '@mui/material';
3
+ import React, { useState } from 'react';
4
+ import ConfirmDialog from './ConfirmDialog';
5
+ import FixedActionsDialog from './FixedActionsDialog';
6
+ import FixedDialog from './FixedDialog';
7
+ import IllustrationDialog from './IllustrationDialog';
8
+ import PermissionDialog from './PermissionDialog';
9
+ import StandardDialog from './StandardDialog';
10
+ import AlertProvider from '../../providers/AlertProvider';
11
+ import { useAlert } from '../../providers/AlertProvider/useAlert';
12
+ import BottomSheet from '../BottomSheet';
13
+ import BottomSheetItem from '../BottomSheet/BottomSheetItem';
14
+ import ListItemText from '../ListItemText';
15
+ const sizes = ['small', 'medium', 'large', 'full'];
16
+ const SHORT_TEXT = 'Ada Lovelace was an English mathematician and writer, chiefly known for her work on the Analytical Engine.';
17
+ const LONG_TEXT = Array.from({ length: 8 }, () => `${SHORT_TEXT} She was the first to recognise that the machine had applications beyond pure calculation, and published the first algorithm intended to be carried out by such a machine. As a result, she is often regarded as the first computer programmer.`).join('\n\n');
18
+ const BACKGROUND = 'linear-gradient(to bottom, rgba(41, 127, 242, 0.24), transparent 160px)';
19
+ const noop = () => undefined;
20
+ const PermissionDialogExample = ({ open, title, content, actions, actionsLayout, onClose }) => (React.createElement(PermissionDialog, { open: open, icon: Cloud, title: typeof title === 'string' ? title : 'PermissionDialog', content: content, actions: actions, actionsLayout: actionsLayout, onClose: onClose }));
21
+ const dialogs = {
22
+ StandardDialog,
23
+ ConfirmDialog,
24
+ FixedDialog,
25
+ FixedActionsDialog,
26
+ IllustrationDialog,
27
+ PermissionDialog: PermissionDialogExample
28
+ };
29
+ const DialogActionsExample = ({ onClose }) => (React.createElement(React.Fragment, null,
30
+ React.createElement(Button, { variant: "outlined", onClick: onClose }, "Close Modal"),
31
+ React.createElement(Button, null, "Do something")));
32
+ const TitleWithButton = () => (React.createElement(React.Fragment, null,
33
+ "This is the title",
34
+ React.createElement(IconButton, { "aria-label": "More" },
35
+ React.createElement(Icon, { icon: Dots }))));
36
+ const titleWithButtonProps = {
37
+ dialogTitle: {
38
+ className: 'u-flex u-flex-items-center u-flex-justify-between u-pv-0-s u-pv-1 u-pr-0-s'
39
+ }
40
+ };
41
+ const InnerElements = () => {
42
+ const { showAlert } = useAlert();
43
+ const [isBottomSheetOpen, setBottomSheetOpen] = useState(false);
44
+ const [isInnerDialogOpen, setInnerDialogOpen] = useState(false);
45
+ const [isInnerConfirmOpen, setInnerConfirmOpen] = useState(false);
46
+ const [isSheetConfirmOpen, setSheetConfirmOpen] = useState(false);
47
+ const [menuAnchor, setMenuAnchor] = useState(null);
48
+ const closeMenu = () => setMenuAnchor(null);
49
+ return (React.createElement(React.Fragment, null,
50
+ React.createElement(Stack, { spacing: 1, sx: { mt: 2, alignItems: 'flex-start' } },
51
+ React.createElement(Button, { onClick: () => showAlert({ message: 'Hello', severity: 'success', duration: 5000 }) }, "Show an alert"),
52
+ React.createElement(Button, { onClick: () => setBottomSheetOpen(true) }, "Show inner bottom sheet"),
53
+ React.createElement(Button, { onClick: () => setInnerConfirmOpen(true) }, "Show inner confirm dialog"),
54
+ React.createElement(Button, { onClick: () => setInnerDialogOpen(true) }, "Show inner dialog"),
55
+ React.createElement(Button, { onClick: event => setMenuAnchor(event.currentTarget) }, "Show menu")),
56
+ React.createElement(Menu, { open: Boolean(menuAnchor), anchorEl: menuAnchor, onClose: closeMenu }, [
57
+ { icon: Pen, label: 'Modify' },
58
+ { icon: People, label: 'People' },
59
+ { icon: Attachment, label: 'Attachment' }
60
+ ].map(({ icon, label }) => (React.createElement(MenuItem, { key: label, onClick: closeMenu },
61
+ React.createElement(ListItemIcon, null,
62
+ React.createElement(Icon, { icon: icon })),
63
+ React.createElement(ListItemText, { primary: label }))))),
64
+ isInnerDialogOpen && (React.createElement(StandardDialog, { open: true, onClose: () => setInnerDialogOpen(false), title: "This is a simple title", content: "This is a simple content" })),
65
+ isInnerConfirmOpen && (React.createElement(ConfirmDialog, { open: true, onClose: () => setInnerConfirmOpen(false), title: "This is a simple title", content: "This is a simple content" })),
66
+ isBottomSheetOpen && (React.createElement(BottomSheet, { backdrop: true, onClose: () => setBottomSheetOpen(false) },
67
+ React.createElement(BottomSheetItem, null,
68
+ React.createElement(Button, { onClick: () => setSheetConfirmOpen(true), sx: { mb: 2 } }, "Show inner confirm dialog"),
69
+ LONG_TEXT,
70
+ isSheetConfirmOpen && (React.createElement(ConfirmDialog, { open: true, onClose: () => setSheetConfirmOpen(false), title: "This is a simple title", content: "This is a simple content" })))))));
71
+ };
72
+ const Playground = ({ dialog, contentLength, longTitle, hideTitle, showActions, withCloseButton, withBackButton, withBackground, titleWithButton, fullScreen, ...props }) => {
73
+ const { showAlert } = useAlert();
74
+ const [open, setOpen] = useState(false);
75
+ const DialogComponent = dialogs[dialog];
76
+ const close = () => setOpen(false);
77
+ const goBack = () => {
78
+ showAlert({ message: 'Back button has been pressed', severity: 'success' });
79
+ close();
80
+ };
81
+ const title = dialog === 'IllustrationDialog' ? (React.createElement(Icon, { icon: Cloud, size: 140 })) : titleWithButton ? (React.createElement(TitleWithButton, null)) : longTitle ? (`${dialog} - ${SHORT_TEXT}`) : (dialog);
82
+ return (React.createElement(React.Fragment, null,
83
+ React.createElement(Button, { onClick: () => setOpen(true) },
84
+ "Open ",
85
+ dialog),
86
+ open && (React.createElement(DialogComponent, { ...props, size: dialog === 'ConfirmDialog' || dialog === 'PermissionDialog'
87
+ ? undefined
88
+ : props.size, open: true, fullScreen: fullScreen || undefined, onClose: withCloseButton ? close : undefined, onBack: withBackButton ? goBack : undefined, title: hideTitle ? undefined : title, componentsProps: titleWithButton ? titleWithButtonProps : undefined, background: withBackground ? BACKGROUND : undefined, content: React.createElement(React.Fragment, null,
89
+ React.createElement(Typography, { component: "div", variant: "body1" }, contentLength === 'long' ? LONG_TEXT : SHORT_TEXT),
90
+ React.createElement(InnerElements, null)), actions: showActions && React.createElement(DialogActionsExample, { onClose: close }) }))));
91
+ };
92
+ const meta = {
93
+ title: 'Dialogs',
94
+ parameters: { layout: 'centered' },
95
+ tags: ['autodocs'],
96
+ decorators: [
97
+ (Story) => (React.createElement(AlertProvider, null,
98
+ React.createElement(Story, null)))
99
+ ],
100
+ argTypes: {
101
+ dialog: { control: 'select', options: Object.keys(dialogs) },
102
+ size: { control: 'select', options: sizes },
103
+ actionsLayout: { control: 'inline-radio', options: ['row', 'column'] },
104
+ align: { control: 'inline-radio', options: ['middle', 'top'] },
105
+ contentLength: { control: 'inline-radio', options: ['short', 'long'] },
106
+ disableTitleAutoPadding: { control: 'boolean' },
107
+ disableGutters: { control: 'boolean' },
108
+ fullScreen: { control: 'boolean' },
109
+ longTitle: { control: 'boolean' },
110
+ hideTitle: { control: 'boolean' },
111
+ showActions: { control: 'boolean' },
112
+ withCloseButton: { control: 'boolean' },
113
+ withBackButton: { control: 'boolean' },
114
+ withBackground: { control: 'boolean' },
115
+ titleWithButton: { control: 'boolean' }
116
+ },
117
+ args: {
118
+ dialog: 'StandardDialog',
119
+ size: 'medium',
120
+ actionsLayout: 'row',
121
+ align: 'middle',
122
+ contentLength: 'short',
123
+ disableTitleAutoPadding: false,
124
+ disableGutters: false,
125
+ fullScreen: false,
126
+ longTitle: false,
127
+ hideTitle: false,
128
+ showActions: true,
129
+ withCloseButton: true,
130
+ withBackButton: false,
131
+ withBackground: false,
132
+ titleWithButton: false
133
+ }
134
+ };
135
+ export default meta;
136
+ export const Default = {
137
+ render: args => React.createElement(Playground, { ...args })
138
+ };
139
+ const screenshotProps = {
140
+ open: true,
141
+ onClose: noop,
142
+ content: SHORT_TEXT,
143
+ actions: React.createElement(DialogActionsExample, { onClose: noop })
144
+ };
145
+ // Visual Regression - one story per dialog, since each one covers the screen
146
+ export const StandardDialogScreenshot = {
147
+ tags: ['argos'],
148
+ parameters: { layout: 'fullscreen' },
149
+ render: () => React.createElement(StandardDialog, { ...screenshotProps, title: "StandardDialog" })
150
+ };
151
+ export const ConfirmDialogScreenshot = {
152
+ tags: ['argos'],
153
+ parameters: { layout: 'fullscreen' },
154
+ render: () => (React.createElement(ConfirmDialog, { ...screenshotProps, title: "Are you sure?", content: "Content of a confirm dialog, precising what the actions will do, and asking the user if she is sure.", actions: React.createElement(React.Fragment, null,
155
+ React.createElement(Button, { variant: "outlined" }, "Close Modal"),
156
+ React.createElement(Button, { color: "error" }, "Do something destructive")) }))
157
+ };
158
+ export const FixedDialogScreenshot = {
159
+ tags: ['argos'],
160
+ parameters: { layout: 'fullscreen' },
161
+ render: () => (React.createElement(FixedDialog, { ...screenshotProps, title: "FixedDialog", content: LONG_TEXT }))
162
+ };
163
+ export const FixedActionsDialogScreenshot = {
164
+ tags: ['argos'],
165
+ parameters: { layout: 'fullscreen' },
166
+ render: () => (React.createElement(FixedActionsDialog, { ...screenshotProps, title: "FixedActionsDialog", content: LONG_TEXT }))
167
+ };
168
+ export const IllustrationDialogScreenshot = {
169
+ tags: ['argos'],
170
+ parameters: { layout: 'fullscreen' },
171
+ render: () => (React.createElement(IllustrationDialog, { ...screenshotProps, size: "small", title: React.createElement(Icon, { icon: Cloud, size: 140 }) }))
172
+ };
173
+ export const PermissionDialogScreenshot = {
174
+ tags: ['argos'],
175
+ parameters: { layout: 'fullscreen' },
176
+ render: () => (React.createElement(PermissionDialogExample, { ...screenshotProps, title: "Are you sure?", content: "Content of a confirm dialog, precising what the actions will do, and asking the user if she is sure." }))
177
+ };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { DialogSectionsProps } from './types';
3
+ import { DialogLayoutProps } from './useDialogLayout';
4
+ export interface FixedActionsDialogProps extends DialogLayoutProps, DialogSectionsProps {
5
+ }
6
+ /** Dialog whose actions stay visible while the title scrolls with a long content */
7
+ declare const FixedActionsDialog: ({ title, content, actions, actionsLayout, ...props }: FixedActionsDialogProps) => React.ReactElement;
8
+ export default FixedActionsDialog;
@@ -0,0 +1,20 @@
1
+ import { DialogActions, DialogContent, DialogTitle, Divider } from '@mui/material';
2
+ import cx from 'classnames';
3
+ import React from 'react';
4
+ import DialogNavButtons from './DialogNavButtons';
5
+ import { useDialogLayout } from './useDialogLayout';
6
+ import Dialog from '../Dialog';
7
+ /** Dialog whose actions stay visible while the title scrolls with a long content */
8
+ const FixedActionsDialog = ({ title, content, actions, actionsLayout, ...props }) => {
9
+ const { id, fullScreen, dialogProps, dialogTitleProps, dividerProps, dialogActionsProps, dialogContentProps } = useDialogLayout({ ...props, isFluidTitle: true });
10
+ return (React.createElement(Dialog, { ...dialogProps },
11
+ React.createElement(DialogNavButtons, { id: id, fullScreen: fullScreen, onClose: props.onClose, onBack: props.onBack }),
12
+ React.createElement(DialogContent, { ...dialogContentProps },
13
+ React.createElement("div", { className: "dialogContentInner" },
14
+ title && (React.createElement("div", { className: "dialogTitleFluidContainer" },
15
+ React.createElement(DialogTitle, { ...dialogTitleProps }, title))),
16
+ content)),
17
+ React.createElement(Divider, { ...dividerProps }),
18
+ actions && (React.createElement(DialogActions, { ...dialogActionsProps, disableSpacing: true, className: cx({ columnLayout: actionsLayout === 'column' }, dialogActionsProps.className) }, actions))));
19
+ };
20
+ export default FixedActionsDialog;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { DialogSectionsProps } from './types';
3
+ import { DialogLayoutProps } from './useDialogLayout';
4
+ export interface FixedDialogProps extends DialogLayoutProps, DialogSectionsProps {
5
+ }
6
+ /** Dialog whose title and actions stay visible while a long content scrolls between them */
7
+ declare const FixedDialog: ({ title, content, actions, actionsLayout, ...props }: FixedDialogProps) => React.ReactElement;
8
+ export default FixedDialog;
@@ -0,0 +1,20 @@
1
+ import { DialogActions, DialogContent, DialogTitle, Divider } from '@mui/material';
2
+ import cx from 'classnames';
3
+ import React from 'react';
4
+ import DialogNavButtons from './DialogNavButtons';
5
+ import { useDialogLayout } from './useDialogLayout';
6
+ import Dialog from '../Dialog';
7
+ /** Dialog whose title and actions stay visible while a long content scrolls between them */
8
+ const FixedDialog = ({ title, content, actions, actionsLayout, ...props }) => {
9
+ const { id, fullScreen, dialogProps, dialogTitleProps, dividerProps, dialogActionsProps, dialogContentProps } = useDialogLayout(props);
10
+ return (React.createElement(Dialog, { ...dialogProps },
11
+ React.createElement(DialogNavButtons, { id: id, fullScreen: fullScreen, onClose: props.onClose, onBack: props.onBack }),
12
+ title && (React.createElement(React.Fragment, null,
13
+ React.createElement(DialogTitle, { ...dialogTitleProps }, title),
14
+ React.createElement(Divider, { ...dividerProps }))),
15
+ React.createElement(DialogContent, { ...dialogContentProps },
16
+ React.createElement("div", { className: "dialogContentInner" }, content)),
17
+ React.createElement(Divider, { ...dividerProps }),
18
+ actions && (React.createElement(DialogActions, { ...dialogActionsProps, disableSpacing: true, className: cx({ columnLayout: actionsLayout === 'column' }, dialogActionsProps.className) }, actions))));
19
+ };
20
+ export default FixedDialog;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { DialogSectionsProps } from './types';
3
+ import { DialogLayoutProps } from './useDialogLayout';
4
+ export interface IllustrationDialogProps extends DialogLayoutProps, DialogSectionsProps {
5
+ }
6
+ /** Dialog whose title is an illustration, centered above a short content */
7
+ declare const IllustrationDialog: ({ title, content, actions, actionsLayout, ...props }: IllustrationDialogProps) => React.ReactElement;
8
+ export default IllustrationDialog;