• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SAP / ui5-webcomponents-react / 9742178811

01 Jul 2024 10:00AM CUT coverage: 81.445% (+0.06%) from 81.387%
9742178811

Pull #5978

github

web-flow
Merge 362695332 into 2cf618399
Pull Request #5978: feat(ThemeProvider): apply Fiori scrollbar styling to all scroll containers

2649 of 3845 branches covered (68.89%)

2 of 2 new or added lines in 2 files covered. (100.0%)

164 existing lines in 4 files now uncovered.

4815 of 5912 relevant lines covered (81.44%)

70456.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.77
/packages/main/src/components/MessageBox/index.tsx
1
'use client';
2

3
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
4
import IconMode from '@ui5/webcomponents/dist/types/IconMode.js';
5
import PopupAccessibleRole from '@ui5/webcomponents/dist/types/PopupAccessibleRole.js';
6
import TitleLevel from '@ui5/webcomponents/dist/types/TitleLevel.js';
7
import ValueState from '@ui5/webcomponents-base/dist/types/ValueState.js';
8
import iconSysHelp from '@ui5/webcomponents-icons/dist/sys-help-2.js';
9
import { useI18nBundle, useIsomorphicId, useStylesheet } from '@ui5/webcomponents-react-base';
10
import { clsx } from 'clsx';
11
import type { ReactElement, ReactNode } from 'react';
12
import { cloneElement, forwardRef, isValidElement } from 'react';
13
import { MessageBoxAction, MessageBoxType } from '../../enums/index.js';
14
import {
15
  ABORT,
16
  CANCEL,
17
  CLOSE,
18
  CONFIRMATION,
19
  DELETE,
20
  ERROR,
21
  IGNORE,
22
  INFORMATION,
23
  NO,
24
  OK,
25
  RETRY,
26
  SUCCESS,
27
  WARNING,
28
  YES
29
} from '../../i18n/i18n-defaults.js';
30
import type { ButtonPropTypes, DialogDomRef, DialogPropTypes } from '../../webComponents/index.js';
31
import { Button, Dialog, Icon, Title } from '../../webComponents/index.js';
32
import { Text } from '../Text/index.js';
33
import { classNames, styleData } from './MessageBox.module.css.js';
34

35
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
36
type MessageBoxActionType = MessageBoxAction | keyof typeof MessageBoxAction | string;
37

38
export interface MessageBoxPropTypes
39
  extends Omit<
40
    DialogPropTypes,
41
    'children' | 'footer' | 'headerText' | 'onClose' | 'state' | 'accessibleNameRef' | 'open' | 'initialFocus'
42
  > {
43
  /**
44
   * Defines the IDs of the elements that label the component.
45
   *
46
   * __Note:__ Per default the prop receives the IDs of the header and the content.
47
   */
48
  accessibleNameRef?: DialogPropTypes['accessibleNameRef'];
49
  /**
50
   * Flag whether the Message Box should be opened or closed
51
   */
52
  open?: DialogPropTypes['open'];
53
  /**
54
   * A custom title for the MessageBox. If not present, it will be derived from the `MessageBox` type.
55
   */
56
  titleText?: DialogPropTypes['headerText'];
57
  /**
58
   * Defines the content of the `MessageBox`.
59
   *
60
   * **Note:** Although this prop accepts HTML Elements, it is strongly recommended that you only use text in order to preserve the intended design and a11y capabilities.
61
   */
62
  children: ReactNode | ReactNode[];
63
  /**
64
   * Array of actions of the MessageBox. Those actions will be transformed into buttons in the `MessageBox` footer.
65
   *
66
   * __Note:__ Although this prop accepts all HTML Elements, it is strongly recommended that you only use `MessageBoxAction`s (text) or the `Button` component in order to preserve the intended.
67
   */
68
  actions?: (MessageBoxActionType | ReactNode)[];
69
  /**
70
   * Specifies which action of the created dialog will be emphasized.
71
   *
72
   * @since 0.16.3
73
   *
74
   * @default `"OK"`
75
   */
76
  emphasizedAction?: MessageBoxActionType;
77
  /**
78
   * A custom icon. If not present, it will be derived from the `MessageBox` type.
79
   */
80
  icon?: ReactNode;
81
  /**
82
   * Defines the type of the `MessageBox` with predefined title, icon, actions and a visual highlight color.
83
   *
84
   * @default `"Confirm"`
85
   */
86
  type?: MessageBoxType | keyof typeof MessageBoxType;
87
  /**
88
   * Defines the ID of the HTML Element or the `MessageBoxAction`, which will get the initial focus.
89
   */
90
  initialFocus?: MessageBoxActionType;
91
  /**
92
   * Callback to be executed when the `MessageBox` is closed (either by pressing on one of the `actions` or by pressing the Escape key).
93
   * `action` is the pressed action button, it's `undefined` when closed via ESC.
94
   */
95
  onClose?: (action: MessageBoxActionType | undefined, escPressed?: true) => void;
96
}
97

98
const getIcon = (icon, type, classes) => {
405✔
99
  if (isValidElement(icon)) return icon;
457✔
100
  switch (type) {
436✔
101
    case MessageBoxType.Confirm:
102
      return <Icon name={iconSysHelp} mode={IconMode.Decorative} className={classes.confirmIcon} />;
262✔
103
    default:
104
      return null;
174✔
105
  }
106
};
107

108
const convertMessageBoxTypeToState = (type: MessageBoxType) => {
405✔
109
  switch (type) {
457✔
110
    case MessageBoxType.Information:
111
      return ValueState.Information;
36✔
112
    case MessageBoxType.Success:
113
      return ValueState.Positive;
66✔
114
    case MessageBoxType.Warning:
115
      return ValueState.Critical;
42✔
116
    case MessageBoxType.Error:
117
      return ValueState.Negative;
39✔
118
    default:
119
      return ValueState.None;
274✔
120
  }
121
};
122

123
const getActions = (actions, type): (string | ReactElement<ButtonPropTypes>)[] => {
405✔
124
  if (actions && actions.length > 0) {
457✔
125
    return actions;
69✔
126
  }
127
  if (type === MessageBoxType.Confirm) {
388✔
128
    return [MessageBoxAction.OK, MessageBoxAction.Cancel];
193✔
129
  }
130
  if (type === MessageBoxType.Error) {
195✔
131
    return [MessageBoxAction.Close];
39✔
132
  }
133
  return [MessageBoxAction.OK];
156✔
134
};
135

136
/**
137
 * The `MessageBox` component provides easier methods to create a `Dialog`, such as standard alerts, confirmation dialogs, or arbitrary message dialogs.
138
 * For convenience, it also provides an `open` prop, so it is not necessary to attach a `ref` to open the `MessageBox`.
139
 */
140
const MessageBox = forwardRef<DialogDomRef, MessageBoxPropTypes>((props, ref) => {
405✔
141
  const {
142
    open,
143
    type = MessageBoxType.Confirm,
179✔
144
    children,
145
    className,
146
    titleText,
147
    icon,
148
    actions = [],
388✔
149
    emphasizedAction = MessageBoxAction.OK,
457✔
150
    onClose,
151
    initialFocus,
152
    ...rest
153
  } = props;
457✔
154

155
  useStylesheet(styleData, MessageBox.displayName);
457✔
156

157
  const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
457✔
158

159
  const actionTranslations = {
457✔
160
    [MessageBoxAction.Abort]: i18nBundle.getText(ABORT),
161
    [MessageBoxAction.Cancel]: i18nBundle.getText(CANCEL),
162
    [MessageBoxAction.Close]: i18nBundle.getText(CLOSE),
163
    [MessageBoxAction.Delete]: i18nBundle.getText(DELETE),
164
    [MessageBoxAction.Ignore]: i18nBundle.getText(IGNORE),
165
    [MessageBoxAction.No]: i18nBundle.getText(NO),
166
    [MessageBoxAction.OK]: i18nBundle.getText(OK),
167
    [MessageBoxAction.Retry]: i18nBundle.getText(RETRY),
168
    [MessageBoxAction.Yes]: i18nBundle.getText(YES)
169
  };
170

171
  const titleToRender = () => {
457✔
172
    if (titleText) {
734✔
173
      return titleText;
90✔
174
    }
175
    switch (type) {
644✔
176
      case MessageBoxType.Confirm:
177
        return i18nBundle.getText(CONFIRMATION);
470✔
178
      case MessageBoxType.Error:
179
        return i18nBundle.getText(ERROR);
39✔
180
      case MessageBoxType.Information:
181
        return i18nBundle.getText(INFORMATION);
36✔
182
      case MessageBoxType.Success:
183
        return i18nBundle.getText(SUCCESS);
45✔
184
      case MessageBoxType.Warning:
185
        return i18nBundle.getText(WARNING);
42✔
186
      default:
187
        return null;
12✔
188
    }
189
  };
190

191
  const handleDialogClose: DialogPropTypes['onBeforeClose'] = (e) => {
457✔
192
    if (typeof props.onBeforeClose === 'function') {
22!
UNCOV
193
      props.onBeforeClose(e);
×
194
    }
195
    if (e.detail.escPressed) {
22✔
196
      onClose(undefined, e.detail.escPressed);
11✔
197
    }
198
  };
199

200
  const handleOnClose: ButtonPropTypes['onClick'] = (e) => {
457✔
201
    const { action } = e.currentTarget.dataset;
145✔
202
    onClose(action);
145✔
203
  };
204

205
  const messageBoxId = useIsomorphicId();
457✔
206
  const internalActions = getActions(actions, type);
457✔
207

208
  const getInitialFocus = () => {
457✔
209
    const actionToFocus = internalActions.find((action) => action === initialFocus);
779✔
210
    if (typeof actionToFocus === 'string') {
457✔
211
      return `${messageBoxId}-action-${actionToFocus}`;
9✔
212
    }
213
    return initialFocus;
448✔
214
  };
215

216
  // @ts-expect-error: footer, headerText and onClose are already omitted via prop types
217
  const { footer: _0, headerText: _1, onClose: _2, onBeforeClose: _3, ...restWithoutOmitted } = rest;
457✔
218

219
  const iconToRender = getIcon(icon, type, classNames);
457✔
220
  const needsCustomHeader = !props.header && !!iconToRender;
457✔
221

222
  return (
457✔
223
    <Dialog
224
      open={open}
225
      ref={ref}
226
      className={clsx(classNames.messageBox, className)}
227
      onBeforeClose={handleDialogClose}
228
      accessibleNameRef={needsCustomHeader ? `${messageBoxId}-title ${messageBoxId}-text` : undefined}
457✔
229
      accessibleRole={PopupAccessibleRole.AlertDialog}
230
      {...restWithoutOmitted}
231
      headerText={titleToRender()}
232
      state={convertMessageBoxTypeToState(type as MessageBoxType)}
233
      initialFocus={getInitialFocus()}
234
      data-type={type}
235
    >
236
      {needsCustomHeader && (
734✔
237
        <div slot="header" className={classNames.header}>
238
          {iconToRender}
239
          {iconToRender && <span className={classNames.spacer} />}
554✔
240
          <Title id={`${messageBoxId}-title`} level={TitleLevel.H1}>
241
            {titleToRender()}
242
          </Title>
243
        </div>
244
      )}
245
      <Text id={`${messageBoxId}-text`}>{children}</Text>
246
      <div slot="footer" className={classNames.footer}>
247
        {internalActions.map((action, index) => {
248
          if (typeof action !== 'string' && isValidElement(action)) {
779✔
249
            return cloneElement<ButtonPropTypes | { 'data-action': string }>(action, {
30✔
250
              onClick: action?.props?.onClick
30!
251
                ? (e) => {
252
                    action?.props?.onClick(e);
10✔
253
                    handleOnClose(e);
10✔
254
                  }
255
                : handleOnClose,
256
              'data-action': action?.props?.['data-action'] ?? `${index}: custom action`
60✔
257
            });
258
          }
259
          if (typeof action === 'string') {
749✔
260
            return (
749✔
261
              <Button
262
                id={`${messageBoxId}-action-${action}`}
263
                key={`${action}-${index}`}
264
                design={emphasizedAction === action ? ButtonDesign.Emphasized : ButtonDesign.Transparent}
749✔
265
                onClick={handleOnClose}
266
                data-action={action}
267
              >
268
                {actionTranslations[action] ?? action}
794✔
269
              </Button>
270
            );
271
          }
UNCOV
272
          return null;
×
273
        })}
274
      </div>
275
    </Dialog>
276
  );
277
});
278

279
MessageBox.displayName = 'MessageBox';
405✔
280

281
export { MessageBox };
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc