@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.
Files changed (58) hide show
  1. package/lib/index.css +55 -33
  2. package/lib/index.d.ts +34 -20
  3. package/lib/index.esm.css +55 -33
  4. package/lib/index.esm.js +1666 -85
  5. package/lib/index.esm.js.map +1 -1
  6. package/lib/index.js +1669 -86
  7. package/lib/index.js.map +1 -1
  8. package/lib/src/components/forms/input/Input.d.ts +5 -7
  9. package/lib/src/components/forms/passwordInput/PasswordInput.d.ts +5 -7
  10. package/lib/src/components/forms/subcomponents/Label.d.ts +6 -3
  11. package/lib/src/components/forms/textarea/Textarea.d.ts +5 -7
  12. package/lib/src/components/index.d.ts +2 -0
  13. package/lib/src/components/menu/Menu.d.ts +5 -0
  14. package/lib/src/components/menu/Menu.stories.d.ts +6 -0
  15. package/lib/src/components/menu/Menu.test.d.ts +1 -0
  16. package/lib/src/components/menu/index.d.ts +1 -0
  17. package/lib/src/components/popper/Popper.d.ts +12 -0
  18. package/lib/src/components/popper/Popper.stories.d.ts +6 -0
  19. package/lib/src/components/popper/Popper.test.d.ts +1 -0
  20. package/lib/src/components/popper/index.d.ts +1 -0
  21. package/lib/src/hooks/useClickOutside.d.ts +2 -0
  22. package/lib/src/index.d.ts +2 -0
  23. package/lib/src/storybook/labelArgTypes.d.ts +3 -0
  24. package/package.json +2 -1
  25. package/src/components/forms/input/Input.mdx +15 -2
  26. package/src/components/forms/input/Input.stories.tsx +10 -45
  27. package/src/components/forms/input/Input.tsx +22 -15
  28. package/src/components/forms/input/styles/Input.scss +0 -11
  29. package/src/components/forms/passwordInput/PasswordInput.mdx +10 -8
  30. package/src/components/forms/passwordInput/PasswordInput.stories.tsx +3 -44
  31. package/src/components/forms/passwordInput/PasswordInput.tsx +20 -15
  32. package/src/components/forms/passwordInput/styles/PasswordInput.scss +0 -11
  33. package/src/components/forms/subcomponents/Label.tsx +29 -6
  34. package/src/components/forms/subcomponents/__tests__/Label.test.tsx +63 -15
  35. package/src/components/forms/textarea/Textarea.mdx +12 -2
  36. package/src/components/forms/textarea/Textarea.stories.tsx +4 -46
  37. package/src/components/forms/textarea/Textarea.tsx +15 -13
  38. package/src/components/forms/textarea/styles/Textarea.scss +0 -11
  39. package/src/components/index.ts +2 -0
  40. package/src/components/menu/Menu.mdx +15 -0
  41. package/src/components/menu/Menu.stories.tsx +56 -0
  42. package/src/components/menu/Menu.test.tsx +88 -0
  43. package/src/components/menu/Menu.tsx +20 -0
  44. package/src/components/menu/index.ts +1 -0
  45. package/src/components/menu/styles/Menu.scss +19 -0
  46. package/src/components/menu/styles/_variables.scss +15 -0
  47. package/src/components/popper/Popper.mdx +79 -0
  48. package/src/components/popper/Popper.stories.tsx +161 -0
  49. package/src/components/popper/Popper.test.tsx +68 -0
  50. package/src/components/popper/Popper.tsx +57 -0
  51. package/src/components/popper/index.ts +1 -0
  52. package/src/components/popper/styles/Popper.scss +11 -0
  53. package/src/components/popper/styles/_variables.scss +15 -0
  54. package/src/hooks/useClickOutside.tsx +22 -0
  55. package/src/index.ts +2 -0
  56. package/src/legacy/components/buttons/commonStyles.ts +0 -4
  57. package/src/storybook/labelArgTypes.ts +50 -0
  58. 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,11 @@
1
+ @import './variables.scss';
2
+
3
+ .popper-container {
4
+ z-index: 99;
5
+ }
6
+
7
+ .popper-content {
8
+ border-radius: var(--pf-popper-border-radius);
9
+ border: 1px solid var(--pf-popper-border-color);
10
+ background: var(--pf-popper-background-color);
11
+ }
@@ -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';
@@ -42,10 +42,6 @@ export const defaults = css`
42
42
  font-family: ${TYPOGRAPHY.fontFamily.base};
43
43
 
44
44
  padding: 0 1em !important;
45
-
46
- & + button {
47
- margin-left: 14px;
48
- }
49
45
  `;
50
46
 
51
47
  export const sizeLarge = css`
@@ -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;
@@ -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';