@indico-data/design-system 2.18.0 → 2.20.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/index.css +55 -33
- package/lib/index.d.ts +34 -20
- package/lib/index.esm.css +55 -33
- package/lib/index.esm.js +1666 -85
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +1669 -86
- package/lib/index.js.map +1 -1
- package/lib/src/components/forms/input/Input.d.ts +5 -7
- package/lib/src/components/forms/passwordInput/PasswordInput.d.ts +5 -7
- package/lib/src/components/forms/subcomponents/Label.d.ts +6 -3
- package/lib/src/components/forms/textarea/Textarea.d.ts +5 -7
- package/lib/src/components/index.d.ts +2 -0
- package/lib/src/components/menu/Menu.d.ts +5 -0
- package/lib/src/components/menu/Menu.stories.d.ts +6 -0
- package/lib/src/components/menu/Menu.test.d.ts +1 -0
- package/lib/src/components/menu/index.d.ts +1 -0
- package/lib/src/components/popper/Popper.d.ts +12 -0
- package/lib/src/components/popper/Popper.stories.d.ts +6 -0
- package/lib/src/components/popper/Popper.test.d.ts +1 -0
- package/lib/src/components/popper/index.d.ts +1 -0
- package/lib/src/hooks/useClickOutside.d.ts +2 -0
- package/lib/src/index.d.ts +2 -0
- package/lib/src/storybook/labelArgTypes.d.ts +3 -0
- package/package.json +2 -1
- package/src/components/forms/input/Input.mdx +15 -2
- package/src/components/forms/input/Input.stories.tsx +10 -45
- package/src/components/forms/input/Input.tsx +22 -15
- package/src/components/forms/input/styles/Input.scss +0 -11
- package/src/components/forms/passwordInput/PasswordInput.mdx +10 -8
- package/src/components/forms/passwordInput/PasswordInput.stories.tsx +3 -44
- package/src/components/forms/passwordInput/PasswordInput.tsx +20 -15
- package/src/components/forms/passwordInput/styles/PasswordInput.scss +0 -11
- package/src/components/forms/subcomponents/Label.tsx +29 -6
- package/src/components/forms/subcomponents/__tests__/Label.test.tsx +63 -15
- package/src/components/forms/textarea/Textarea.mdx +12 -2
- package/src/components/forms/textarea/Textarea.stories.tsx +4 -46
- package/src/components/forms/textarea/Textarea.tsx +15 -13
- package/src/components/forms/textarea/styles/Textarea.scss +0 -11
- package/src/components/index.ts +2 -0
- package/src/components/menu/Menu.mdx +15 -0
- package/src/components/menu/Menu.stories.tsx +56 -0
- package/src/components/menu/Menu.test.tsx +88 -0
- package/src/components/menu/Menu.tsx +20 -0
- package/src/components/menu/index.ts +1 -0
- package/src/components/menu/styles/Menu.scss +19 -0
- package/src/components/menu/styles/_variables.scss +15 -0
- package/src/components/popper/Popper.mdx +79 -0
- package/src/components/popper/Popper.stories.tsx +161 -0
- package/src/components/popper/Popper.test.tsx +68 -0
- package/src/components/popper/Popper.tsx +57 -0
- package/src/components/popper/index.ts +1 -0
- package/src/components/popper/styles/Popper.scss +11 -0
- package/src/components/popper/styles/_variables.scss +15 -0
- package/src/hooks/useClickOutside.tsx +22 -0
- package/src/index.ts +2 -0
- package/src/legacy/components/buttons/commonStyles.ts +0 -4
- package/src/storybook/labelArgTypes.ts +50 -0
- package/src/styles/index.scss +2 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { useState, useRef } from 'react';
|
|
3
|
+
import { Popper, PopperProps } from './Popper';
|
|
4
|
+
import { Button } from '../button';
|
|
5
|
+
import { Menu } from '../menu';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Popper> = {
|
|
8
|
+
title: 'Components/Popper',
|
|
9
|
+
component: Popper,
|
|
10
|
+
argTypes: {
|
|
11
|
+
children: {
|
|
12
|
+
control: 'object',
|
|
13
|
+
description: 'The content of the popper.',
|
|
14
|
+
table: {
|
|
15
|
+
category: 'Props',
|
|
16
|
+
type: {
|
|
17
|
+
summary: 'React.ReactNode',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
referenceElement: {
|
|
22
|
+
control: 'object',
|
|
23
|
+
description: 'The element to which the popper is attached.',
|
|
24
|
+
table: {
|
|
25
|
+
category: 'Props',
|
|
26
|
+
type: {
|
|
27
|
+
summary: 'HTMLElement | null',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
isOpen: {
|
|
32
|
+
control: 'boolean',
|
|
33
|
+
description: 'Controls the visibility of the popper.',
|
|
34
|
+
table: {
|
|
35
|
+
category: 'Props',
|
|
36
|
+
type: {
|
|
37
|
+
summary: 'boolean',
|
|
38
|
+
},
|
|
39
|
+
defaultValue: { summary: 'false' },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
onClose: {
|
|
43
|
+
action: 'closed',
|
|
44
|
+
description: 'Callback function when the popper closes.',
|
|
45
|
+
table: { category: 'callbacks' },
|
|
46
|
+
},
|
|
47
|
+
ariaLabel: {
|
|
48
|
+
control: 'text',
|
|
49
|
+
description: 'Sets the aria-label attribute for the popper.',
|
|
50
|
+
table: {
|
|
51
|
+
category: 'Props',
|
|
52
|
+
type: {
|
|
53
|
+
summary: 'string',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
placement: {
|
|
58
|
+
control: 'select',
|
|
59
|
+
options: [
|
|
60
|
+
'top',
|
|
61
|
+
'top-start',
|
|
62
|
+
'top-end',
|
|
63
|
+
'right',
|
|
64
|
+
'right-start',
|
|
65
|
+
'right-end',
|
|
66
|
+
'bottom',
|
|
67
|
+
'bottom-start',
|
|
68
|
+
'bottom-end',
|
|
69
|
+
'left',
|
|
70
|
+
'left-start',
|
|
71
|
+
'left-end',
|
|
72
|
+
],
|
|
73
|
+
description: 'Sets the placement of the popper.',
|
|
74
|
+
table: {
|
|
75
|
+
category: 'Props',
|
|
76
|
+
type: {
|
|
77
|
+
summary: 'string',
|
|
78
|
+
},
|
|
79
|
+
defaultValue: { summary: 'bottom-start' },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
offsetValue: {
|
|
83
|
+
control: 'number',
|
|
84
|
+
description: 'Sets the offset value for the popper.',
|
|
85
|
+
table: {
|
|
86
|
+
category: 'Props',
|
|
87
|
+
type: {
|
|
88
|
+
summary: 'number',
|
|
89
|
+
},
|
|
90
|
+
defaultValue: { summary: '5' },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
decorators: [
|
|
95
|
+
(Story: React.ComponentType) => (
|
|
96
|
+
<div
|
|
97
|
+
style={{
|
|
98
|
+
height: '160px',
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<Story />
|
|
102
|
+
</div>
|
|
103
|
+
),
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export default meta;
|
|
108
|
+
|
|
109
|
+
type Story = StoryObj<PopperProps>;
|
|
110
|
+
|
|
111
|
+
export const Default: Story = {
|
|
112
|
+
render: (args) => {
|
|
113
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
114
|
+
const buttonRef = useRef<HTMLDivElement | null>(null);
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div>
|
|
118
|
+
<div ref={buttonRef}>
|
|
119
|
+
<Button
|
|
120
|
+
onClick={() => setIsOpen((prev) => !prev)}
|
|
121
|
+
iconName="kabob"
|
|
122
|
+
ariaLabel="Toggle Popper"
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
<Popper
|
|
126
|
+
{...args}
|
|
127
|
+
referenceElement={buttonRef.current}
|
|
128
|
+
isOpen={isOpen || args.isOpen}
|
|
129
|
+
onClose={() => setIsOpen(false)}
|
|
130
|
+
>
|
|
131
|
+
<Menu>
|
|
132
|
+
<Button
|
|
133
|
+
data-testid="refresh-library"
|
|
134
|
+
ariaLabel="Refresh Data"
|
|
135
|
+
iconName="retrain"
|
|
136
|
+
onClick={() => console.log('Refresh Data')}
|
|
137
|
+
>
|
|
138
|
+
Refresh Data
|
|
139
|
+
</Button>
|
|
140
|
+
<Button
|
|
141
|
+
data-testid="configure-fields"
|
|
142
|
+
ariaLabel="Configure Fields"
|
|
143
|
+
iconName="edit"
|
|
144
|
+
onClick={() => console.log('Configure Fields')}
|
|
145
|
+
>
|
|
146
|
+
Configure Fields
|
|
147
|
+
</Button>
|
|
148
|
+
<Button
|
|
149
|
+
data-testid="delete-library"
|
|
150
|
+
ariaLabel="Delete Library"
|
|
151
|
+
iconName="trash"
|
|
152
|
+
onClick={() => console.log('Delete Library')}
|
|
153
|
+
>
|
|
154
|
+
Delete Library
|
|
155
|
+
</Button>
|
|
156
|
+
</Menu>
|
|
157
|
+
</Popper>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
},
|
|
161
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import { Popper } from './Popper';
|
|
4
|
+
import { Menu } from '../menu';
|
|
5
|
+
import { Button } from '../button';
|
|
6
|
+
|
|
7
|
+
describe('Popper Component', () => {
|
|
8
|
+
it('renders without display when isOpen is false', () => {
|
|
9
|
+
render(
|
|
10
|
+
<Popper referenceElement={null} isOpen={false} onClose={jest.fn()} ariaLabel="Example Popper">
|
|
11
|
+
<div>Popper Content</div>
|
|
12
|
+
</Popper>,
|
|
13
|
+
);
|
|
14
|
+
expect(screen.queryByText('Popper Content')).not.toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('displays the popper content when isOpen is true', () => {
|
|
18
|
+
render(
|
|
19
|
+
<Popper referenceElement={null} isOpen={true} onClose={jest.fn()} ariaLabel="Example Popper">
|
|
20
|
+
<div>Popper Content</div>
|
|
21
|
+
</Popper>,
|
|
22
|
+
);
|
|
23
|
+
expect(screen.getByText('Popper Content')).toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('calls onClose when clicked outside', () => {
|
|
27
|
+
const mockOnClose = jest.fn();
|
|
28
|
+
|
|
29
|
+
render(
|
|
30
|
+
<div>
|
|
31
|
+
<div data-testid="outside">Outside Element</div>
|
|
32
|
+
<Popper
|
|
33
|
+
referenceElement={null}
|
|
34
|
+
isOpen={true}
|
|
35
|
+
onClose={mockOnClose}
|
|
36
|
+
ariaLabel="Example Popper"
|
|
37
|
+
>
|
|
38
|
+
<div>Popper Content</div>
|
|
39
|
+
</Popper>
|
|
40
|
+
</div>,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
fireEvent.mouseDown(screen.getByTestId('outside'));
|
|
44
|
+
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('renders children inside the popper', () => {
|
|
48
|
+
render(
|
|
49
|
+
<Popper referenceElement={null} isOpen={true} onClose={jest.fn()} ariaLabel="Example Popper">
|
|
50
|
+
<Menu>
|
|
51
|
+
<Button ariaLabel="Refresh Data" iconName="retrain">
|
|
52
|
+
Refresh Data
|
|
53
|
+
</Button>
|
|
54
|
+
<Button ariaLabel="Configure Fields" iconName="edit">
|
|
55
|
+
Configure Fields
|
|
56
|
+
</Button>
|
|
57
|
+
<Button ariaLabel="Delete Library" iconName="trash">
|
|
58
|
+
Delete Library
|
|
59
|
+
</Button>
|
|
60
|
+
</Menu>
|
|
61
|
+
</Popper>,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(screen.getByText('Refresh Data')).toBeInTheDocument();
|
|
65
|
+
expect(screen.getByText('Configure Fields')).toBeInTheDocument();
|
|
66
|
+
expect(screen.getByText('Delete Library')).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import { useFloating, offset, flip, shift, Placement } from '@floating-ui/react-dom';
|
|
3
|
+
import { useClickOutside } from '@/hooks/useClickOutside';
|
|
4
|
+
|
|
5
|
+
export type PopperProps = {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
referenceElement: HTMLElement | null;
|
|
8
|
+
isOpen: boolean;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
ariaLabel: string;
|
|
11
|
+
placement?: Placement;
|
|
12
|
+
offsetValue?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function Popper({
|
|
16
|
+
children,
|
|
17
|
+
referenceElement,
|
|
18
|
+
isOpen,
|
|
19
|
+
onClose,
|
|
20
|
+
ariaLabel,
|
|
21
|
+
placement = 'bottom-start',
|
|
22
|
+
offsetValue = 5,
|
|
23
|
+
}: PopperProps) {
|
|
24
|
+
const popperContentRef = useRef() as React.MutableRefObject<HTMLDivElement>;
|
|
25
|
+
|
|
26
|
+
const { x, y, strategy, refs } = useFloating({
|
|
27
|
+
placement,
|
|
28
|
+
middleware: [offset(offsetValue), flip(), shift()],
|
|
29
|
+
elements: {
|
|
30
|
+
reference: referenceElement,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
useClickOutside(popperContentRef, onClose);
|
|
35
|
+
|
|
36
|
+
if (!isOpen) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
ref={refs.setFloating}
|
|
43
|
+
style={{
|
|
44
|
+
position: strategy,
|
|
45
|
+
top: y ?? '',
|
|
46
|
+
left: x ?? '',
|
|
47
|
+
}}
|
|
48
|
+
role="dialog"
|
|
49
|
+
aria-label={ariaLabel}
|
|
50
|
+
className="popper-container"
|
|
51
|
+
>
|
|
52
|
+
<div ref={popperContentRef} className="popper-content">
|
|
53
|
+
{children}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Popper } from './Popper';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Common Variables
|
|
2
|
+
:root,
|
|
3
|
+
:root [data-theme='light'],
|
|
4
|
+
:root [data-theme='dark'] {
|
|
5
|
+
--pf-popper-background-color: var(--pf-white-color);
|
|
6
|
+
--pf-popper-border-color: var(--pf-gray-color-900);
|
|
7
|
+
--pf-popper-border-radius: var(--pf-rounded);
|
|
8
|
+
--pf-popper-padding: var(--pf-padding-2);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Dark Theme Specific Variables
|
|
12
|
+
:root [data-theme='dark'] {
|
|
13
|
+
--pf-popper-background-color: var(--pf-primary-color-600);
|
|
14
|
+
--pf-popper-border-color: var(--pf-gray-color);
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useClickOutside = (
|
|
4
|
+
ref: React.MutableRefObject<HTMLElement>,
|
|
5
|
+
handler: (e: MouseEvent | TouchEvent) => void,
|
|
6
|
+
) => {
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const listener = (e: MouseEvent | TouchEvent) => {
|
|
9
|
+
if (!ref.current || ref.current.contains(e.target as Node)) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
handler(e);
|
|
13
|
+
};
|
|
14
|
+
document.addEventListener('mousedown', listener);
|
|
15
|
+
document.addEventListener('touchstart', listener);
|
|
16
|
+
|
|
17
|
+
return () => {
|
|
18
|
+
document.removeEventListener('mousedown', listener);
|
|
19
|
+
document.removeEventListener('touchstart', listener);
|
|
20
|
+
};
|
|
21
|
+
}, [ref, handler]);
|
|
22
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -71,3 +71,5 @@ export { Select as SelectInput } from './components/forms/select';
|
|
|
71
71
|
export { Form } from './components/forms/form';
|
|
72
72
|
export { Skeleton } from './components/skeleton';
|
|
73
73
|
export { Card } from './components/card';
|
|
74
|
+
export { Popper } from './components/popper';
|
|
75
|
+
export { Menu } from './components/menu';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Common argTypes for form components that include a label
|
|
2
|
+
import { ArgTypes } from '@storybook/react';
|
|
3
|
+
|
|
4
|
+
const labelArgTypes: ArgTypes = {
|
|
5
|
+
label: {
|
|
6
|
+
control: 'text',
|
|
7
|
+
description: 'The text to display as the label for the form component',
|
|
8
|
+
table: {
|
|
9
|
+
category: 'Props',
|
|
10
|
+
type: {
|
|
11
|
+
summary: 'string',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
isRequired: {
|
|
16
|
+
control: 'boolean',
|
|
17
|
+
description: 'Toggles the required asterisk on the label',
|
|
18
|
+
table: {
|
|
19
|
+
category: 'Props',
|
|
20
|
+
type: {
|
|
21
|
+
summary: 'boolean',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultValue: { summary: 'false' },
|
|
25
|
+
},
|
|
26
|
+
hasHiddenLabel: {
|
|
27
|
+
control: 'boolean',
|
|
28
|
+
description:
|
|
29
|
+
'Determines whether the label should be displayed or not. Included as aria-label if true',
|
|
30
|
+
table: {
|
|
31
|
+
category: 'Props',
|
|
32
|
+
type: {
|
|
33
|
+
summary: 'boolean',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
defaultValue: { summary: 'false' },
|
|
37
|
+
},
|
|
38
|
+
name: {
|
|
39
|
+
control: 'text',
|
|
40
|
+
description: 'The name attribute for the form component, used for identifying the field',
|
|
41
|
+
table: {
|
|
42
|
+
category: 'Props',
|
|
43
|
+
type: {
|
|
44
|
+
summary: 'string',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default labelArgTypes;
|
package/src/styles/index.scss
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
@import '../components/forms/toggle/styles/Toggle.scss';
|
|
17
17
|
@import '../components/skeleton/styles/Skeleton.scss';
|
|
18
18
|
@import '../components/card/styles/Card.scss';
|
|
19
|
+
@import '../components/menu/styles/Menu.scss';
|
|
20
|
+
@import '../components/popper/styles/Popper.scss';
|
|
19
21
|
@import '../legacy/components/inputs/NoInputDatePicker/NoInputDatePicker.scss';
|
|
20
22
|
@import 'typography';
|
|
21
23
|
@import 'colors';
|