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

SAP / ui5-webcomponents-react / 14304969491

07 Apr 2025 08:53AM CUT coverage: 87.54% (+0.08%) from 87.46%
14304969491

Pull #7187

github

web-flow
Merge f9635f7ca into 8d652b4c7
Pull Request #7187: feat(ObjectPage): allow customizing `role` of `footerArea` container

2966 of 3932 branches covered (75.43%)

5185 of 5923 relevant lines covered (87.54%)

84562.43 hits per line

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

88.64
/packages/main/src/components/MessageView/MessageItem.tsx
1
'use client';
2

3
import IconMode from '@ui5/webcomponents/dist/types/IconMode.js';
4
import ListItemType from '@ui5/webcomponents/dist/types/ListItemType.js';
5
import WrappingType from '@ui5/webcomponents/dist/types/WrappingType.js';
6
import ValueState from '@ui5/webcomponents-base/dist/types/ValueState.js';
7
import iconArrowRight from '@ui5/webcomponents-icons/dist/slim-arrow-right.js';
8
import { useI18nBundle, useStylesheet } from '@ui5/webcomponents-react-base';
9
import { clsx } from 'clsx';
10
import type { ReactNode } from 'react';
11
import { Children, isValidElement, forwardRef, useContext, useEffect, useRef, useState } from 'react';
12
import { FlexBoxAlignItems, FlexBoxDirection } from '../../enums/index.js';
13
import { COUNTER, HAS_DETAILS } from '../../i18n/i18n-defaults.js';
14
import { MessageViewContext } from '../../internal/MessageViewContext.js';
15
import type { CommonProps } from '../../types/index.js';
16
import { Icon } from '../../webComponents/Icon/index.js';
17
import { Label } from '../../webComponents/Label/index.js';
18
import type { LinkPropTypes } from '../../webComponents/Link/index.js';
19
import type { ListItemCustomDomRef, ListItemCustomPropTypes } from '../../webComponents/ListItemCustom/index.js';
20
import { ListItemCustom } from '../../webComponents/ListItemCustom/index.js';
21
import { FlexBox } from '../FlexBox/index.js';
22
import { classNames, styleData } from './MessageItem.module.css.js';
23
import { getIconNameForType, getValueStateMap } from './utils.js';
24

25
export interface MessageItemPropTypes extends Pick<ListItemCustomPropTypes, 'accessibleName' | 'tooltip'>, CommonProps {
26
  /**
27
   * Specifies the title of the message
28
   *
29
   * __Note:__ Although this prop accepts all HTML Elements, it is strongly recommended that you only use text or `Link` (with `wrappingType="None"`) in order to preserve the intended design.
30
   */
31
  titleText: ReactNode;
32

33
  /**
34
   * Specifies the subtitle of the message
35
   *
36
   * __Note:__ Although this prop accepts all HTML Elements, it is strongly recommended that you only use text in order to preserve the intended design.
37
   */
38
  subtitleText?: ReactNode;
39

40
  /**
41
   * Defines the number of messages for a given message type.
42
   */
43
  counter?: number;
44

45
  /**
46
   * Specifies the type of the message
47
   */
48
  type?: ValueState | keyof typeof ValueState;
49

50
  /**
51
   * Name of a message group the current item belongs to.
52
   */
53
  groupName?: string;
54

55
  /**
56
   * Specifies detailed description of the message
57
   */
58
  children?: ReactNode | ReactNode[];
59
}
60

61
/**
62
 * A component used to hold different types of system messages inside the `MessageView` component.
63
 */
64
const MessageItem = forwardRef<ListItemCustomDomRef, MessageItemPropTypes>((props, ref) => {
427✔
65
  const { titleText, subtitleText, counter, type = ValueState.Negative, children, className, ...rest } = props;
988✔
66
  const [isTitleTextOverflowing, setIsTitleTextIsOverflowing] = useState(false);
988✔
67
  const [titleTextStr, setTitleTextStr] = useState('');
988✔
68
  const titleTextRef = useRef<HTMLSpanElement>(null);
988✔
69
  const hasDetails = !!(children || isTitleTextOverflowing);
988✔
70
  const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
988✔
71

72
  useStylesheet(styleData, MessageItem.displayName);
988✔
73

74
  const { selectMessage } = useContext(MessageViewContext);
988✔
75

76
  const listItemClasses = clsx(
988✔
77
    classNames.listItem,
78
    Reflect.get(classNames, `type${type}`),
79
    className,
80
    subtitleText && classNames.withSubtitle
1,000✔
81
  );
82

83
  const messageClasses = clsx(classNames.message, hasDetails && classNames.withChildren);
988✔
84

85
  const handleListItemClick = (e) => {
988✔
86
    if (hasDetails) {
19✔
87
      selectMessage({ ...props, titleTextStr });
17✔
88
      if (typeof rest.onClick === 'function') {
17!
89
        rest.onClick(e);
×
90
      }
91
    }
92
  };
93

94
  const handleKeyDown: ListItemCustomPropTypes['onKeyDown'] = (e) => {
988✔
95
    if (typeof rest.onKeyDown === 'function') {
×
96
      rest.onKeyDown(e);
×
97
    }
98
    if (e.code === 'Enter') {
×
99
      handleListItemClick(e);
×
100
    }
101
  };
102

103
  const hasChildren = Children.count(children);
988✔
104
  useEffect(() => {
988✔
105
    const titleTextObserver = new ResizeObserver(([titleTextSpanEntry]) => {
420✔
106
      const child = titleTextSpanEntry.target.children[0];
30✔
107
      const target = titleTextSpanEntry.target;
30✔
108
      const isTargetOverflowing = target.scrollWidth > target.clientWidth;
30✔
109
      let isChildOverflowing = false;
30✔
110

111
      if (!isTargetOverflowing) {
30✔
112
        const firstChild = child?.shadowRoot?.firstChild as HTMLAnchorElement | undefined;
18✔
113
        if (firstChild) {
18✔
114
          isChildOverflowing = firstChild.scrollWidth > firstChild.clientWidth;
12✔
115
        }
116
      }
117
      setIsTitleTextIsOverflowing(isTargetOverflowing || isChildOverflowing);
30✔
118
    });
119
    if (!hasChildren && titleTextRef.current) {
420✔
120
      titleTextObserver.observe(titleTextRef.current);
36✔
121
    }
122
    return () => {
420✔
123
      titleTextObserver.disconnect();
402✔
124
    };
125
  }, [hasChildren]);
126

127
  useEffect(() => {
988✔
128
    if (typeof titleText === 'string') {
420✔
129
      setTitleTextStr(titleText);
408✔
130
    } else if (isValidElement(titleText) && typeof (titleText.props as LinkPropTypes)?.children === 'string') {
12✔
131
      // @ts-expect-error: props.children is available and a string
132
      setTitleTextStr(titleText.props.children);
12✔
133
    }
134
  }, [titleText]);
135

136
  return (
988✔
137
    <ListItemCustom
138
      onClick={handleListItemClick}
139
      onKeyDown={handleKeyDown}
140
      data-title={titleTextStr}
141
      data-type={type}
142
      type={hasDetails ? ListItemType.Active : ListItemType.Inactive}
988✔
143
      {...rest}
144
      className={listItemClasses}
145
      ref={ref}
146
    >
147
      <FlexBox alignItems={FlexBoxAlignItems.Center} className={messageClasses}>
148
        <div className={classNames.iconContainer}>
149
          <Icon name={getIconNameForType(type as ValueState)} className={classNames.icon} mode={IconMode.Decorative} />
150
        </div>
151
        <FlexBox
152
          direction={FlexBoxDirection.Column}
153
          style={{ flex: 'auto', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}
154
        >
155
          {titleText && (
1,976✔
156
            <span className={classNames.title} ref={titleTextRef}>
157
              {titleText}
158
            </span>
159
          )}
160
          {titleText && subtitleText && (
1,988✔
161
            <Label className={classNames.subtitle} wrappingType={WrappingType.None}>
162
              {subtitleText}
163
            </Label>
164
          )}
165
        </FlexBox>
166
        {counter != null && (
1,048✔
167
          <span className={classNames.counter} aria-label={`. ${i18nBundle.getText(COUNTER)} ${counter}`}>
168
            {counter}
169
          </span>
170
        )}
171
        {hasDetails && <Icon className={classNames.navigation} name={iconArrowRight} mode={IconMode.Decorative} />}
1,892✔
172
        {hasDetails && <span className={classNames.pseudoInvisibleText}>. {i18nBundle.getText(HAS_DETAILS)}</span>}
1,892✔
173
        {type !== ValueState.None && type !== ValueState.Information && (
2,496✔
174
          <span className={classNames.pseudoInvisibleText}>. {getValueStateMap(i18nBundle)[type]}</span>
175
        )}
176
      </FlexBox>
177
    </ListItemCustom>
178
  );
179
});
180

181
MessageItem.displayName = 'MessageItem';
427✔
182

183
export { MessageItem };
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