@indico-data/design-system 2.55.2 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indico-data/design-system",
3
- "version": "2.55.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",
@@ -10,7 +10,7 @@ const meta: Meta<typeof FloatUI> = {
10
10
  component: FloatUI,
11
11
  argTypes: {
12
12
  children: {
13
- control: 'object',
13
+ control: false,
14
14
  description:
15
15
  'An array of exactly two elements: the first element is the trigger that opens the FloatUI, and the second element is the content displayed within the FloatUI.',
16
16
  table: {
@@ -31,7 +31,7 @@ const meta: Meta<typeof FloatUI> = {
31
31
  },
32
32
  },
33
33
  floatingOptions: {
34
- control: 'object',
34
+ control: false,
35
35
  description:
36
36
  'Options for configuring the floating UI behavior. For more, see the [floating-ui docs](https://floating-ui.com/docs/useFloating#options).',
37
37
  table: {
@@ -52,7 +52,7 @@ const meta: Meta<typeof FloatUI> = {
52
52
  },
53
53
  },
54
54
  isPortal: {
55
- control: 'boolean',
55
+ control: false,
56
56
  defaultValue: false,
57
57
  description:
58
58
  'Controls whether the FloatUI content is rendered as a portal (i.e. rendered outside the app root and into the body)',
@@ -64,7 +64,7 @@ const meta: Meta<typeof FloatUI> = {
64
64
  },
65
65
  },
66
66
  portalOptions: {
67
- control: 'object',
67
+ control: false,
68
68
  description:
69
69
  'Options for configuring the portal behavior. Includes the rootId, which is the id of the root element to render the portal into.',
70
70
  table: {
@@ -103,6 +103,9 @@ export default meta;
103
103
  type Story = StoryObj<FloatUIProps>;
104
104
 
105
105
  export const Uncontrolled: Story = {
106
+ args: {
107
+ ariaLabel: 'Example FloatUI',
108
+ },
106
109
  render: (args) => (
107
110
  <FloatUI {...args} ariaLabel="Example FloatUI">
108
111
  <Button iconLeft="kabob" ariaLabel="Toggle FloatUI" variant="action" />
@@ -82,7 +82,7 @@ export const DatePicker = (props: DatePickerProps) => {
82
82
  const finalProps = { ...commonProps, ...rest, ...modeProps };
83
83
 
84
84
  return (
85
- <>
85
+ <div className="date-picker-wrapper">
86
86
  {hasTimePicker && (
87
87
  <div className="time-picker-wrapper">
88
88
  <Row align="center">
@@ -96,6 +96,6 @@ export const DatePicker = (props: DatePickerProps) => {
96
96
  </div>
97
97
  )}
98
98
  <DayPicker {...finalProps} />
99
- </>
99
+ </div>
100
100
  );
101
101
  };
@@ -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';
@@ -6,8 +6,18 @@ const meta: Meta<typeof Menu> = {
6
6
  title: 'Components/Menu',
7
7
  component: Menu,
8
8
  argTypes: {
9
- children: {
9
+ className: {
10
10
  control: 'text',
11
+ description: 'The class name to apply to the Menu component.',
12
+ table: {
13
+ category: 'Props',
14
+ type: {
15
+ summary: 'string',
16
+ },
17
+ },
18
+ },
19
+ children: {
20
+ control: false,
11
21
  description:
12
22
  'The children of the Menu component, which will be automatically styled as menu-items.',
13
23
  table: {
@@ -25,6 +35,9 @@ export default meta;
25
35
  type Story = StoryObj<MenuProps>;
26
36
 
27
37
  export const Default: Story = {
38
+ args: {
39
+ className: 'custom-menu',
40
+ },
28
41
  render: (args) => (
29
42
  <Menu {...args}>
30
43
  <Button
@@ -11,6 +11,17 @@
11
11
  width: 100%;
12
12
  text-align: left;
13
13
  border: none;
14
+ border-radius: 0;
15
+
16
+ &:first-child {
17
+ border-top-left-radius: var(--pf-menu-rounded);
18
+ border-top-right-radius: var(--pf-menu-rounded);
19
+ }
20
+
21
+ &:last-child {
22
+ border-bottom-left-radius: var(--pf-menu-rounded);
23
+ border-bottom-right-radius: var(--pf-menu-rounded);
24
+ }
14
25
 
15
26
  &:hover {
16
27
  background: var(--pf-menu-item-hover-color);
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ export interface TruncateProps {
2
+ lineClamp?: number;
3
+ truncateString: string;
4
+ hasTooltip?: boolean;
5
+ tooltipId?: string;
6
+ [key: string]: any;
7
+ }
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
 
@@ -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';
@@ -61,6 +61,7 @@
61
61
  --pf-gray-color-700: #262626;
62
62
  --pf-gray-color-800: #171717;
63
63
  --pf-gray-color-900: #0a0a0a;
64
+ --pf-gray-color-950: #000000; // MISSING
64
65
 
65
66
  //Red color
66
67
  --pf-red-color: #ce6068;
@@ -71,6 +72,7 @@
71
72
  --pf-red-color-300: #ebb6b8;
72
73
  --pf-red-color-350: #f39bb9;
73
74
  --pf-red-color-400: #ef76a0;
75
+ --pf-red-color-450: #df8d91;
74
76
  --pf-red-color-500: #ce6068;
75
77
  --pf-red-color-600: #b94553;
76
78
  --pf-red-color-700: #9b3544;
@@ -156,6 +158,23 @@
156
158
  --pf-white-color-90: rgba(255, 255, 255, 0.9);
157
159
  --pf-white-color-100: rgba(255, 255, 255, 1);
158
160
 
161
+ // Black color
162
+ --pf-black-color: #000000;
163
+ --pf-black-color-1: rgba(0, 0, 0, 0.01);
164
+ --pf-black-color-3: rgba(0, 0, 0, 0.03);
165
+ --pf-black-color-5: rgba(0, 0, 0, 0.05);
166
+ --pf-black-color-10: rgba(0, 0, 0, 0.1);
167
+ --pf-black-color-15: rgba(0, 0, 0, 0.15);
168
+ --pf-black-color-20: rgba(0, 0, 0, 0.2);
169
+ --pf-black-color-30: rgba(0, 0, 0, 0.3);
170
+ --pf-black-color-40: rgba(0, 0, 0, 0.4);
171
+ --pf-black-color-50: rgba(0, 0, 0, 0.5);
172
+ --pf-black-color-60: rgba(0, 0, 0, 0.6);
173
+ --pf-black-color-70: rgba(0, 0, 0, 0.7);
174
+ --pf-black-color-80: rgba(0, 0, 0, 0.8);
175
+ --pf-black-color-90: rgba(0, 0, 0, 0.9);
176
+ --pf-black-color-100: rgba(0, 0, 0, 1);
177
+
159
178
  // Utility Colors
160
179
  --pf-error-color: var(--pf-red-color);
161
180
  --pf-success-color: var(--pf-green-color);