@indico-data/design-system 2.56.0 → 2.57.0
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.
- package/lib/components/index.d.ts +1 -0
- package/lib/components/truncate/Truncate.d.ts +2 -0
- package/lib/components/truncate/Truncate.stories.d.ts +9 -0
- package/lib/components/truncate/__tests__/Truncate.test.d.ts +1 -0
- package/lib/components/truncate/index.d.ts +1 -0
- package/lib/components/truncate/types.d.ts +7 -0
- package/lib/index.css +21 -0
- package/lib/index.d.ts +11 -1
- package/lib/index.esm.css +21 -0
- package/lib/index.esm.js +53 -1
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +53 -0
- package/lib/index.js.map +1 -1
- package/package.json +2 -1
- package/src/components/index.ts +1 -0
- package/src/components/truncate/Truncate.mdx +34 -0
- package/src/components/truncate/Truncate.stories.tsx +86 -0
- package/src/components/truncate/Truncate.tsx +55 -0
- package/src/components/truncate/__tests__/Truncate.test.tsx +61 -0
- package/src/components/truncate/index.ts +1 -0
- package/src/components/truncate/styles/Truncate.scss +22 -0
- package/src/components/truncate/types.ts +7 -0
- package/src/index.ts +1 -1
- package/src/styles/index.scss +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indico-data/design-system",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.57.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"date-fns": "^3.6.0",
|
|
46
46
|
"focus-trap-react": "^10.2.3",
|
|
47
47
|
"html-webpack-plugin": "^5.6.0",
|
|
48
|
+
"nanoid": "^5.1.5",
|
|
48
49
|
"prop-types": "^15.8.1",
|
|
49
50
|
"react-aria-components": "^1.2.1",
|
|
50
51
|
"react-data-table-component": "^7.6.2",
|
package/src/components/index.ts
CHANGED
|
@@ -25,3 +25,4 @@ export { TanstackTable } from './tanstackTable';
|
|
|
25
25
|
export { Tooltip } from './tooltip';
|
|
26
26
|
export { BarSpinner } from './loading-indicators/BarSpinner/BarSpinner';
|
|
27
27
|
export { CirclePulse } from './loading-indicators/CirclePulse/CirclePulse';
|
|
28
|
+
export { Truncate } from './truncate';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Canvas, Meta, Controls, Story } from '@storybook/blocks';
|
|
2
|
+
import * as Truncate from './Truncate.stories';
|
|
3
|
+
|
|
4
|
+
<Meta title="Utilities/Truncate" name="Truncate" of={Truncate} />
|
|
5
|
+
|
|
6
|
+
# Truncate
|
|
7
|
+
|
|
8
|
+
The truncate component accepts a string prop and will truncate the string to fit the available space. It will add an `...` ellipsis to the end of the string. If the truncation is detected by the observer, it will show a tooltip by default, unless the `hasTooltip` prop is set to `false`.
|
|
9
|
+
|
|
10
|
+
<Canvas of={Truncate.Default} />
|
|
11
|
+
|
|
12
|
+
### The following props are available for the Truncate component:
|
|
13
|
+
|
|
14
|
+
<Controls of={Truncate.Default} />
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
Simply pass in a string to the `truncateString` prop.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### With Line Clamp
|
|
22
|
+
|
|
23
|
+
If you want to truncate the text to a specific number of lines, set the `lineClamp` prop to the number of lines you want to truncate to.
|
|
24
|
+
|
|
25
|
+
<Canvas of={Truncate.WithLineClamp} />
|
|
26
|
+
|
|
27
|
+
### No Tooltip
|
|
28
|
+
|
|
29
|
+
If you want to disable the tooltip, set the `hasTooltip` prop to `false`.
|
|
30
|
+
|
|
31
|
+
<Canvas of={Truncate.NoTooltip} />
|
|
32
|
+
|
|
33
|
+
### Notes on Tooltips
|
|
34
|
+
If a duplicate ID is provided, the tooltip will only show on the first instance of the ID.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Truncate } from './Truncate';
|
|
3
|
+
import { TruncateProps } from './types';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Truncate> = {
|
|
6
|
+
title: 'Utilities/Truncate',
|
|
7
|
+
component: Truncate,
|
|
8
|
+
decorators: [
|
|
9
|
+
(Story) => (
|
|
10
|
+
<div style={{ width: '300px' }}>
|
|
11
|
+
<Story />
|
|
12
|
+
</div>
|
|
13
|
+
),
|
|
14
|
+
],
|
|
15
|
+
argTypes: {
|
|
16
|
+
truncateString: {
|
|
17
|
+
required: true,
|
|
18
|
+
control: 'text',
|
|
19
|
+
description:
|
|
20
|
+
'The string to truncate. This value will also be displayed in the tooltip when the text is truncated.',
|
|
21
|
+
table: {
|
|
22
|
+
category: 'Props',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
lineClamp: {
|
|
26
|
+
control: 'number',
|
|
27
|
+
defaultValue: 0,
|
|
28
|
+
description:
|
|
29
|
+
'The number of lines to truncate the text to. If left blank, it will default to 1 line.',
|
|
30
|
+
table: {
|
|
31
|
+
category: 'Props',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
tooltipId: {
|
|
35
|
+
control: 'text',
|
|
36
|
+
description:
|
|
37
|
+
'The id of the tooltip. If an ID is not provided, it will generate one from nanoid',
|
|
38
|
+
table: {
|
|
39
|
+
category: 'Props',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
hasTooltip: {
|
|
43
|
+
control: 'boolean',
|
|
44
|
+
defaultValue: true,
|
|
45
|
+
description:
|
|
46
|
+
'Whether to show the tooltip. If left blank, it will default to true. If set to false, the tooltip will not be shown.',
|
|
47
|
+
table: {
|
|
48
|
+
category: 'Props',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default meta;
|
|
55
|
+
|
|
56
|
+
type Story = StoryObj<TruncateProps>;
|
|
57
|
+
|
|
58
|
+
export const Default: Story = {
|
|
59
|
+
args: {
|
|
60
|
+
lineClamp: 0,
|
|
61
|
+
hasTooltip: true,
|
|
62
|
+
tooltipId: 'truncate-tooltip',
|
|
63
|
+
truncateString: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.',
|
|
64
|
+
},
|
|
65
|
+
render: (args) => <Truncate {...args} />,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const WithLineClamp: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
lineClamp: 2,
|
|
71
|
+
hasTooltip: true,
|
|
72
|
+
tooltipId: 'truncate-tooltip-line-clamp',
|
|
73
|
+
truncateString: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.',
|
|
74
|
+
},
|
|
75
|
+
render: (args) => <Truncate {...args} />,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const NoTooltip: Story = {
|
|
79
|
+
args: {
|
|
80
|
+
lineClamp: 2,
|
|
81
|
+
hasTooltip: false,
|
|
82
|
+
tooltipId: 'truncate-tooltip-no-tooltip',
|
|
83
|
+
truncateString: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.',
|
|
84
|
+
},
|
|
85
|
+
render: (args) => <Truncate {...args} />,
|
|
86
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
import { useState, useEffect, CSSProperties } from 'react';
|
|
3
|
+
import { Tooltip } from '../tooltip';
|
|
4
|
+
import { TruncateProps } from './types';
|
|
5
|
+
|
|
6
|
+
export const Truncate = ({
|
|
7
|
+
lineClamp = 0,
|
|
8
|
+
truncateString,
|
|
9
|
+
hasTooltip = true,
|
|
10
|
+
tooltipId,
|
|
11
|
+
...rest
|
|
12
|
+
}: TruncateProps) => {
|
|
13
|
+
const [isTruncated, setIsTruncated] = useState(false);
|
|
14
|
+
const id = (tooltipId ?? nanoid()).replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const checkTruncation = () => {
|
|
18
|
+
const element = document.querySelector(`[data-tooltip-id="${id}"]`);
|
|
19
|
+
if (element) {
|
|
20
|
+
if (lineClamp === 0) {
|
|
21
|
+
setIsTruncated(element.scrollWidth > element.clientWidth);
|
|
22
|
+
} else {
|
|
23
|
+
setIsTruncated(element.scrollHeight > element.clientHeight);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
checkTruncation();
|
|
29
|
+
window.addEventListener('resize', checkTruncation);
|
|
30
|
+
return () => window.removeEventListener('resize', checkTruncation);
|
|
31
|
+
}, [id, lineClamp]);
|
|
32
|
+
|
|
33
|
+
const truncateStyle = {
|
|
34
|
+
'--line-clamp': lineClamp,
|
|
35
|
+
} as CSSProperties;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="truncate-wrapper" style={truncateStyle}>
|
|
39
|
+
<span
|
|
40
|
+
data-testid={`truncate-${id}-${isTruncated ? 'truncated' : 'not-truncated'}`}
|
|
41
|
+
data-tooltip-id={id}
|
|
42
|
+
data-tooltip-content={isTruncated ? truncateString : undefined}
|
|
43
|
+
className={lineClamp > 0 ? 'truncate-clip' : 'truncate'}
|
|
44
|
+
{...rest}
|
|
45
|
+
>
|
|
46
|
+
{truncateString}
|
|
47
|
+
</span>
|
|
48
|
+
{isTruncated && truncateString && hasTooltip && (
|
|
49
|
+
<Tooltip data-tooltip-delay-hide={100} id={id} border="solid 1px var(--pf-border-color)">
|
|
50
|
+
{truncateString}
|
|
51
|
+
</Tooltip>
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { Truncate } from '../Truncate';
|
|
3
|
+
|
|
4
|
+
// Mock nanoid to return a predictable ID
|
|
5
|
+
jest.mock('nanoid', () => ({
|
|
6
|
+
nanoid: () => 'test-id',
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe('Truncate', () => {
|
|
10
|
+
beforeAll(() => {
|
|
11
|
+
// Mock offsetWidth and scrollWidth
|
|
12
|
+
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
|
|
13
|
+
configurable: true,
|
|
14
|
+
value: 100,
|
|
15
|
+
});
|
|
16
|
+
Object.defineProperty(HTMLElement.prototype, 'scrollWidth', {
|
|
17
|
+
configurable: true,
|
|
18
|
+
value: 200, // larger than offsetWidth to simulate overflow
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterAll(() => {
|
|
23
|
+
jest.restoreAllMocks();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Only way to test is by checking testid, validated this works in app
|
|
27
|
+
it('shows truncated state when content overflows', () => {
|
|
28
|
+
render(<Truncate truncateString="This is a very long text that should be truncated" />);
|
|
29
|
+
|
|
30
|
+
// Now we can use exact IDs instead of regex
|
|
31
|
+
expect(screen.getByTestId('truncate-test-id-truncated')).toBeInTheDocument();
|
|
32
|
+
|
|
33
|
+
// Verify tooltip appears when truncated
|
|
34
|
+
expect(screen.getByTestId('truncate-test-id-truncated')).toHaveAttribute(
|
|
35
|
+
'data-tooltip-content',
|
|
36
|
+
'This is a very long text that should be truncated',
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('shows non-truncated state when content fits', () => {
|
|
41
|
+
// Override both scrollWidth and clientWidth to simulate content fitting
|
|
42
|
+
Object.defineProperty(HTMLElement.prototype, 'scrollWidth', {
|
|
43
|
+
configurable: true,
|
|
44
|
+
value: 100,
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
|
|
47
|
+
configurable: true,
|
|
48
|
+
value: 200,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
render(<Truncate truncateString="Short text" />);
|
|
52
|
+
|
|
53
|
+
// Now we can use exact IDs
|
|
54
|
+
expect(screen.getByTestId('truncate-test-id-not-truncated')).toBeInTheDocument();
|
|
55
|
+
|
|
56
|
+
// Verify no tooltip when not truncated
|
|
57
|
+
expect(screen.getByTestId('truncate-test-id-not-truncated')).not.toHaveAttribute(
|
|
58
|
+
'data-tooltip-content',
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Truncate } from './Truncate';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.truncate-wrapper {
|
|
2
|
+
width: 100%;
|
|
3
|
+
display: block;
|
|
4
|
+
|
|
5
|
+
.truncate {
|
|
6
|
+
white-space: nowrap;
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
text-overflow: ellipsis;
|
|
9
|
+
width: 100%;
|
|
10
|
+
display: inline-block;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.truncate-clip {
|
|
14
|
+
display: -webkit-box;
|
|
15
|
+
-webkit-box-orient: vertical;
|
|
16
|
+
-webkit-line-clamp: var(--line-clamp);
|
|
17
|
+
line-clamp: var(--line-clamp);
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
text-overflow: ellipsis;
|
|
20
|
+
width: 100%;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ export { Tooltip } from './components/tooltip';
|
|
|
32
32
|
export { Pagination } from './components/pagination';
|
|
33
33
|
export { CirclePulse } from './components/loading-indicators/CirclePulse';
|
|
34
34
|
export { BarSpinner } from './components/loading-indicators/BarSpinner/BarSpinner';
|
|
35
|
-
|
|
35
|
+
export { Truncate } from './components/truncate';
|
|
36
36
|
// Utilities
|
|
37
37
|
export { registerFontAwesomeIcons } from './setup/setupIcons';
|
|
38
38
|
|
package/src/styles/index.scss
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
@import '../components/tooltip/styles/Tooltip.scss';
|
|
30
30
|
@import '../components/loading-indicators/BarSpinner/styles/BarSpinner.scss';
|
|
31
31
|
@import '../components/loading-indicators/CirclePulse/CirclePulse.scss';
|
|
32
|
+
@import '../components/truncate/styles/Truncate.scss';
|
|
32
33
|
@import 'sheets'; // Port to an sheets component when we build it
|
|
33
34
|
@import 'typography';
|
|
34
35
|
@import 'colors';
|