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

CBIIT / bento-c3dc-frontend / 25695561366

09 Feb 2026 03:26PM UTC coverage: 0.167%. First build
25695561366

push

github

web-flow
Merge pull request #490 from CBIIT/1.8.0

1.8.0 Release

6 of 3680 branches covered (0.16%)

Branch coverage included in aggregate %.

0 of 925 new or added lines in 39 files covered. (0.0%)

10 of 5876 relevant lines covered (0.17%)

0.09 hits per line

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

0.0
/src/components/EllipsisText/EllipsisText.js
NEW
1
import React, { useRef, useState, useLayoutEffect } from 'react';
×
NEW
2
import { withStyles } from '@material-ui/core';
×
3

4
/**
5
 * EllipsisText Component
6
 * Displays text with ellipsis when it overflows. Supports two modes:
7
 * - 'middle': Shows beginning and end with ellipsis in the middle
8
 * - 'end': Standard end truncation (uses CSS text-overflow: ellipsis)
9
 *
10
 * @param {string} text - The text to display
11
 * @param {string} className - Optional CSS class name
12
 * @param {object} classes - Material-UI classes object
13
 * @param {function} onTruncate - Optional callback called with true when text is truncated, false when not
14
 * @param {string} mode - Ellipsis mode: 'middle' or 'end' (default: 'middle')
15
 *
16
 * Example (middle mode):
17
 * "My Very Long Cohort Name That Exceeds...For Display Copy (3)"
18
 *
19
 * Example (end mode):
20
 * "My Very Long Cohort Name That Exceeds For Display..."
21
 */
NEW
22
const EllipsisText = ({ text, className, classes, onTruncate, mode = 'middle' }) => {
×
NEW
23
    const containerRef = useRef(null);
×
NEW
24
    const measureRef = useRef(null);
×
NEW
25
    const [displayText, setDisplayText] = useState(text);
×
26

27
    // Constants for middle ellipsis truncation
NEW
28
    const MIN_CHARS_PER_SIDE = 5; // Minimum characters to show on each side of ellipsis
×
NEW
29
    const MIN_TOTAL_CHARS = MIN_CHARS_PER_SIDE * 2; // Minimum total characters (start + end)
×
30

NEW
31
    useLayoutEffect(() => {
×
NEW
32
        if (!containerRef.current || !text) {
×
NEW
33
            setDisplayText(text);
×
NEW
34
            return;
×
35
        }
36

NEW
37
        const container = containerRef.current;
×
38

NEW
39
        const measureAndTruncate = () => {
×
NEW
40
            if (!container) return;
×
41

42
            // End mode: Simple overflow detection, CSS handles truncation
NEW
43
            if (mode === 'end') {
×
NEW
44
                const isOverflowing = container.scrollWidth > container.clientWidth;
×
NEW
45
                if (onTruncate) onTruncate(isOverflowing);
×
NEW
46
                setDisplayText(text);
×
NEW
47
                return;
×
48
            }
49

50
            // Middle mode: Custom truncation logic
NEW
51
            if (!measureRef.current) {
×
NEW
52
                setDisplayText(text);
×
NEW
53
                return;
×
54
            }
55

NEW
56
            const measureSpan = measureRef.current;
×
NEW
57
            const availableWidth = container.offsetWidth;
×
58

NEW
59
            if (availableWidth === 0) {
×
NEW
60
                setDisplayText(text);
×
NEW
61
                return;
×
62
            }
63

64
            // Measure full text width
NEW
65
            measureSpan.textContent = text;
×
NEW
66
            const fullWidth = measureSpan.offsetWidth;
×
67

68
            // If text fits, no truncation needed
NEW
69
            if (fullWidth <= availableWidth) {
×
NEW
70
                setDisplayText(text);
×
NEW
71
                if (onTruncate) onTruncate(false);
×
NEW
72
                return;
×
73
            }
74

75
            // Text needs truncation with middle ellipsis
NEW
76
            const ellipsis = '...';
×
NEW
77
            measureSpan.textContent = ellipsis;
×
NEW
78
            const ellipsisWidth = measureSpan.offsetWidth;
×
NEW
79
            const targetWidth = availableWidth - ellipsisWidth;
×
80

NEW
81
            let bestFit = '';
×
82

83
            // Use binary search to find the maximum number of characters that fit (O(log n) complexity)
NEW
84
            let left = MIN_TOTAL_CHARS;
×
NEW
85
            let right = text.length - MIN_CHARS_PER_SIDE;
×
86

NEW
87
            while (left <= right) {
×
NEW
88
                const totalChars = Math.floor((left + right) / 2);
×
89

90
                // Split as evenly as possible (50/50)
NEW
91
                const startLen = Math.floor(totalChars / 2);
×
NEW
92
                const endLen = totalChars - startLen;
×
93

NEW
94
                if (startLen < MIN_CHARS_PER_SIDE || endLen < MIN_CHARS_PER_SIDE) {
×
NEW
95
                    right = totalChars - 1;
×
NEW
96
                    continue;
×
97
                }
98

NEW
99
                const start = text.substring(0, startLen);
×
NEW
100
                const end = text.substring(text.length - endLen);
×
101

NEW
102
                measureSpan.textContent = start + end;
×
NEW
103
                const combinedWidth = measureSpan.offsetWidth;
×
104

NEW
105
                if (combinedWidth <= targetWidth) {
×
106
                    // This fits, try to fit more characters
NEW
107
                    bestFit = start + ellipsis + end;
×
NEW
108
                    left = totalChars + 1;
×
109
                } else {
110
                    // Too wide, try fewer characters
NEW
111
                    right = totalChars - 1;
×
112
                }
113
            }
114

NEW
115
            if (bestFit) {
×
NEW
116
                setDisplayText(bestFit);
×
NEW
117
                if (onTruncate) onTruncate(true);
×
118
            } else {
119
                // Fallback: just show first part with ellipsis
NEW
120
                let truncateLen = Math.floor(text.length * 0.5);
×
NEW
121
                while (truncateLen > 1) {
×
NEW
122
                    const truncated = text.substring(0, truncateLen) + ellipsis;
×
NEW
123
                    measureSpan.textContent = truncated;
×
NEW
124
                    if (measureSpan.offsetWidth <= availableWidth) {
×
NEW
125
                        setDisplayText(truncated);
×
NEW
126
                        if (onTruncate) onTruncate(true);
×
NEW
127
                        return;
×
128
                    }
NEW
129
                    truncateLen--;
×
130
                }
NEW
131
                setDisplayText(text.substring(0, 1) + ellipsis);
×
NEW
132
                if (onTruncate) onTruncate(true);
×
133
            }
134
        };
135

136
        // Use ResizeObserver to react to actual layout changes
NEW
137
        const resizeObserver = new ResizeObserver((entries) => {
×
138
            // Only measure when container has stable, non-zero dimensions
NEW
139
            const entry = entries[0];
×
NEW
140
            if (entry && entry.contentRect.width > 0) {
×
NEW
141
                measureAndTruncate();
×
142
            }
143
        });
144

NEW
145
        resizeObserver.observe(container);
×
146

147
        // Also run immediately in case container is already sized
NEW
148
        if (container.offsetWidth > 0) {
×
NEW
149
            measureAndTruncate();
×
150
        }
151

NEW
152
        return () => {
×
NEW
153
            resizeObserver.disconnect();
×
154
        };
155
    }, [text, mode, onTruncate, MIN_CHARS_PER_SIDE, MIN_TOTAL_CHARS]);
156

157
    // End mode: Simple span with CSS truncation
NEW
158
    if (mode === 'end') {
×
NEW
159
        return (
×
160
            <span
161
                ref={containerRef}
162
                className={`${classes.endContainer} ${className || ''}`}
×
163
            >
164
                {displayText}
165
            </span>
166
        );
167
    }
168

169
    // Middle mode: Custom truncation with measurement span
NEW
170
    return (
×
171
        <span ref={containerRef} className={`${classes.middleContainer} ${className || ''}`}>
×
172
            <span className={classes.measureSpan} ref={measureRef} />
173
            <span className={classes.displaySpan}>{displayText}</span>
174
        </span>
175
    );
176
};
177

NEW
178
const styles = () => ({
×
179
    endContainer: {
180
        overflow: 'hidden',
181
        textOverflow: 'ellipsis',
182
        whiteSpace: 'nowrap',
183
        display: 'inline-block',
184
        maxWidth: '100%',
185
    },
186
    middleContainer: {
187
        position: 'relative',
188
        display: 'inline-block',
189
        maxWidth: '100%',
190
    },
191
    measureSpan: {
192
        visibility: 'hidden',
193
        position: 'absolute',
194
        whiteSpace: 'nowrap',
195
    },
196
    displaySpan: {
197
        display: 'inline-block',
198
        maxWidth: '100%',
199
    },
200
});
201

NEW
202
const StyledEllipsisText = withStyles(styles)(EllipsisText);
×
203

204
// Export convenient named variants
NEW
205
export const MiddleEllipsisText = (props) => <StyledEllipsisText {...props} mode="middle" />;
×
NEW
206
export const EndEllipsisText = (props) => <StyledEllipsisText {...props} mode="end" />;
×
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

© 2026 Coveralls, Inc