@indico-data/design-system 3.14.1 → 3.16.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/button/enums.d.ts +1 -1
- package/lib/components/button/types.d.ts +1 -1
- package/lib/components/icons/types.d.ts +1 -1
- package/lib/index.css +35 -28
- package/lib/index.d.ts +2 -2
- package/lib/index.esm.css +35 -28
- package/lib/index.esm.js +28 -7
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +28 -7
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/button/Button.mdx +24 -1
- package/src/components/button/Button.stories.tsx +7 -2
- package/src/components/button/Button.tsx +26 -16
- package/src/components/button/__tests__/Button.test.tsx +64 -11
- package/src/components/button/enums.ts +1 -1
- package/src/components/button/styles/Button.scss +19 -12
- package/src/components/button/styles/_variables.scss +2 -9
- package/src/components/button/types.ts +1 -1
- package/src/components/floatUI/FloatUI.stories.tsx +5 -0
- package/src/components/floatUI/FloatUI.tsx +5 -1
- package/src/components/forms/input/Input.tsx +7 -2
- package/src/components/forms/input/styles/Input.scss +4 -2
- package/src/components/forms/numberInput/NumberInput.tsx +7 -2
- package/src/components/forms/passwordInput/PasswordInput.tsx +2 -2
- package/src/components/icons/Icon.mdx +1 -1
- package/src/components/icons/Icon.stories.tsx +2 -2
- package/src/components/icons/styles/Icon.scss +1 -0
- package/src/components/icons/types.ts +1 -1
- package/src/components/menu/Menu.stories.tsx +3 -0
- package/src/components/menu/styles/Menu.scss +2 -2
- package/src/components/pagination/Pagination.tsx +2 -0
- package/src/components/stepper/components/Legend.tsx +1 -1
- package/src/components/toast/Toast.stories.tsx +5 -5
- package/src/docs/BaseColorPalette/Swatch.tsx +2 -2
- package/src/docs/Primitives.mdx +7 -1
- package/src/styles/primitives/_iconSizes.scss +4 -4
package/package.json
CHANGED
|
@@ -20,7 +20,23 @@ Use the event props to respond to interactions, including `onBlur` for focus los
|
|
|
20
20
|
|
|
21
21
|
## Sizes
|
|
22
22
|
|
|
23
|
-
The Sizes available are small (`xs`),
|
|
23
|
+
The Sizes available are extra small (`xs`), small (`sm`), medium (`md`), large (`lg`), and extra large (`xl`). The default size is medium (`md`).
|
|
24
|
+
|
|
25
|
+
Each size has specific padding and icon spacing:
|
|
26
|
+
|
|
27
|
+
- **xs**: Vertical padding `spacing-xxs` (4px), horizontal padding `spacing-sm` (8px), icon gap `spacing-xs` (6px)
|
|
28
|
+
- **sm**: Vertical padding `spacing-xs` (6px), horizontal padding `spacing-lg` (12px), icon gap `spacing-xs` (6px)
|
|
29
|
+
- **md**: Vertical padding `spacing-sm` (8px), horizontal padding `spacing-2xl` (16px), icon gap `spacing-md` (10px)
|
|
30
|
+
- **lg**: Vertical padding `spacing-md` (10px), horizontal padding `spacing-2xl` (16px), icon gap `spacing-md` (10px)
|
|
31
|
+
- **xl**: Vertical padding `spacing-lg` (12px), horizontal padding `spacing-3xl` (20px), icon gap `spacing-md` (10px)
|
|
32
|
+
|
|
33
|
+
Icon sizes are mapped to button sizes as follows:
|
|
34
|
+
|
|
35
|
+
- **xs** button → **xs** icon (12px)
|
|
36
|
+
- **sm** button → **sm** icon (16px)
|
|
37
|
+
- **md** button → **md** icon (20px) - text is 13px
|
|
38
|
+
- **lg** button → **md** icon (20px)
|
|
39
|
+
- **xl** button → **lg** icon (24px) - text is 20px
|
|
24
40
|
|
|
25
41
|
<Story of={ButtonStories.Sizes} />
|
|
26
42
|
|
|
@@ -52,4 +68,11 @@ The loading state can be achieved by passing the `isLoading` prop.
|
|
|
52
68
|
|
|
53
69
|
To include an Icon, use either `iconLeft` or `iconRight` props with a valid icon name. If you only want an icon with no text, simply don't pass any children and the button will render as an icon-only button.
|
|
54
70
|
|
|
71
|
+
Icon spacing is automatically handled by the button's CSS `gap` property, which varies by button size:
|
|
72
|
+
|
|
73
|
+
- **xs** and **sm** buttons: `spacing-xs` (6px) gap between icons and text
|
|
74
|
+
- **md**, **lg**, and **xl** buttons: `spacing-md` (10px) gap between icons and text
|
|
75
|
+
|
|
76
|
+
Icon sizes are automatically mapped based on button size (see the Sizes section above), so you don't need to specify icon sizes separately.
|
|
77
|
+
|
|
55
78
|
<Story of={ButtonStories.Icons} />
|
|
@@ -87,13 +87,13 @@ const meta: Meta = {
|
|
|
87
87
|
},
|
|
88
88
|
size: {
|
|
89
89
|
control: 'select',
|
|
90
|
-
options: ['xs', 'sm', 'md', 'lg'],
|
|
90
|
+
options: ['xs', 'sm', 'md', 'lg', 'xl'],
|
|
91
91
|
|
|
92
92
|
defaultValue: { summary: 'md' },
|
|
93
93
|
table: {
|
|
94
94
|
category: 'Style Props',
|
|
95
95
|
type: {
|
|
96
|
-
summary: 'xs | sm | md | lg',
|
|
96
|
+
summary: 'xs | sm | md | lg | xl',
|
|
97
97
|
},
|
|
98
98
|
},
|
|
99
99
|
},
|
|
@@ -200,6 +200,11 @@ export const Sizes: Story = {
|
|
|
200
200
|
Large
|
|
201
201
|
</Button>
|
|
202
202
|
</Col>
|
|
203
|
+
<Col xs={6} sm={3} className="mb-3">
|
|
204
|
+
<Button size="xl" ariaLabel="Extra Large Button">
|
|
205
|
+
Extra Large
|
|
206
|
+
</Button>
|
|
207
|
+
</Col>
|
|
203
208
|
</Row>
|
|
204
209
|
),
|
|
205
210
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { Icon } from '../icons/Icon';
|
|
4
|
+
import { IconSizes } from '../icons/types';
|
|
4
5
|
|
|
5
6
|
import { ButtonProps } from './types';
|
|
6
7
|
|
|
@@ -35,6 +36,27 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) =>
|
|
|
35
36
|
className,
|
|
36
37
|
);
|
|
37
38
|
|
|
39
|
+
// Map button size to icon size
|
|
40
|
+
// xs → xs (12px), sm → sm (16px), md → md (20px), lg → md (20px), xl → lg (24px)
|
|
41
|
+
const getIconSize = (buttonSize: string): IconSizes => {
|
|
42
|
+
switch (buttonSize) {
|
|
43
|
+
case 'xs':
|
|
44
|
+
return 'xs';
|
|
45
|
+
case 'sm':
|
|
46
|
+
return 'sm';
|
|
47
|
+
case 'md':
|
|
48
|
+
return 'md'; // md buttons use md icons (20px)
|
|
49
|
+
case 'lg':
|
|
50
|
+
return 'md'; // lg buttons use md icons (20px)
|
|
51
|
+
case 'xl':
|
|
52
|
+
return 'lg'; // xl buttons use lg icons (24px)
|
|
53
|
+
default:
|
|
54
|
+
return 'md';
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const iconSize = getIconSize(size);
|
|
59
|
+
|
|
38
60
|
const handleOnClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
39
61
|
if (!isLoading && onClick) {
|
|
40
62
|
onClick(event);
|
|
@@ -60,20 +82,14 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) =>
|
|
|
60
82
|
<Icon
|
|
61
83
|
name="indico-o"
|
|
62
84
|
style={{ animation: 'spin 1s linear infinite' }}
|
|
63
|
-
className={children ? 'mr-2' : ''}
|
|
64
85
|
ariaLabel="Loading..."
|
|
65
|
-
size={
|
|
86
|
+
size={iconSize}
|
|
66
87
|
/>
|
|
67
88
|
)}
|
|
68
89
|
|
|
69
90
|
{/* Left Icon */}
|
|
70
91
|
{iconLeft && !isLoading && (
|
|
71
|
-
<Icon
|
|
72
|
-
name={iconLeft}
|
|
73
|
-
className={children ? 'mr-2' : ''}
|
|
74
|
-
ariaLabel={`${iconLeft} Icon`}
|
|
75
|
-
size={size}
|
|
76
|
-
/>
|
|
92
|
+
<Icon name={iconLeft} ariaLabel={`${iconLeft} Icon`} size={iconSize} />
|
|
77
93
|
)}
|
|
78
94
|
|
|
79
95
|
{/* Button children */}
|
|
@@ -81,12 +97,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) =>
|
|
|
81
97
|
|
|
82
98
|
{/* Right Icon */}
|
|
83
99
|
{iconRight && !isLoading && (
|
|
84
|
-
<Icon
|
|
85
|
-
name={iconRight}
|
|
86
|
-
className={children ? 'ml-2' : ''}
|
|
87
|
-
ariaLabel={`${iconRight} Icon`}
|
|
88
|
-
size={size}
|
|
89
|
-
/>
|
|
100
|
+
<Icon name={iconRight} ariaLabel={`${iconRight} Icon`} size={iconSize} />
|
|
90
101
|
)}
|
|
91
102
|
|
|
92
103
|
{/* Loading Icon on the right */}
|
|
@@ -94,9 +105,8 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) =>
|
|
|
94
105
|
<Icon
|
|
95
106
|
name="indico-o"
|
|
96
107
|
style={{ animation: 'spin 1s linear infinite' }}
|
|
97
|
-
className={children ? 'ml-2' : ''}
|
|
98
108
|
ariaLabel="Loading..."
|
|
99
|
-
size={
|
|
109
|
+
size={iconSize}
|
|
100
110
|
/>
|
|
101
111
|
)}
|
|
102
112
|
</button>
|
|
@@ -57,7 +57,6 @@ describe('Button', () => {
|
|
|
57
57
|
expect(button).toHaveClass('btn--loading');
|
|
58
58
|
expect(button).toBeDisabled();
|
|
59
59
|
expect(loadingIcon).toBeInTheDocument();
|
|
60
|
-
expect(loadingIcon).toHaveClass('mr-2');
|
|
61
60
|
});
|
|
62
61
|
|
|
63
62
|
it('displays the loading icon on the left when isLoading and iconLeft exists and hides the iconLeft', () => {
|
|
@@ -69,7 +68,6 @@ describe('Button', () => {
|
|
|
69
68
|
const loadingIcon = screen.getByLabelText('Loading...');
|
|
70
69
|
const iconLeft = screen.queryByLabelText('check Icon');
|
|
71
70
|
|
|
72
|
-
expect(loadingIcon).toHaveClass('mr-2');
|
|
73
71
|
expect(iconLeft).not.toBeInTheDocument();
|
|
74
72
|
});
|
|
75
73
|
|
|
@@ -82,19 +80,16 @@ describe('Button', () => {
|
|
|
82
80
|
const loadingIcon = screen.getByLabelText('Loading...');
|
|
83
81
|
const iconRight = screen.queryByLabelText('check Icon');
|
|
84
82
|
|
|
85
|
-
expect(loadingIcon).toHaveClass('ml-2');
|
|
86
83
|
expect(iconRight).not.toBeInTheDocument();
|
|
87
84
|
});
|
|
88
85
|
|
|
89
86
|
it('does not apply a margin to the loading icon when no children are present', () => {
|
|
90
87
|
render(<Button isLoading iconLeft="check" onClick={onClick} ariaLabel="btn" />);
|
|
91
88
|
const loadingIcon = screen.getByLabelText('Loading...');
|
|
92
|
-
expect(loadingIcon).
|
|
93
|
-
expect(loadingIcon).not.toHaveClass('ml-2');
|
|
89
|
+
expect(loadingIcon).toBeInTheDocument();
|
|
94
90
|
|
|
95
91
|
render(<Button isLoading iconRight="check" onClick={onClick} ariaLabel="btn" />);
|
|
96
|
-
expect(loadingIcon).
|
|
97
|
-
expect(loadingIcon).not.toHaveClass('ml-2');
|
|
92
|
+
expect(loadingIcon).toBeInTheDocument();
|
|
98
93
|
});
|
|
99
94
|
|
|
100
95
|
it('does not apply the loading class when not loading', () => {
|
|
@@ -156,7 +151,6 @@ describe('Button', () => {
|
|
|
156
151
|
const button = screen.getByRole('button');
|
|
157
152
|
|
|
158
153
|
expect(button).toContainElement(icon);
|
|
159
|
-
expect(icon).toHaveClass('mr-2');
|
|
160
154
|
});
|
|
161
155
|
|
|
162
156
|
it('renders a right icon with proper spacing when children are present', () => {
|
|
@@ -169,7 +163,6 @@ describe('Button', () => {
|
|
|
169
163
|
const button = screen.getByRole('button');
|
|
170
164
|
|
|
171
165
|
expect(button).toContainElement(icon);
|
|
172
|
-
expect(icon).toHaveClass('ml-2');
|
|
173
166
|
});
|
|
174
167
|
|
|
175
168
|
it('renders both left and right icons with proper spacing when both are provided', () => {
|
|
@@ -184,8 +177,68 @@ describe('Button', () => {
|
|
|
184
177
|
|
|
185
178
|
expect(button).toContainElement(leftIcon);
|
|
186
179
|
expect(button).toContainElement(rightIcon);
|
|
187
|
-
|
|
188
|
-
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('icon size mapping', () => {
|
|
184
|
+
it('uses xs icon size for xs button', () => {
|
|
185
|
+
render(
|
|
186
|
+
<Button size="xs" iconLeft="check" ariaLabel="xs button">
|
|
187
|
+
Button
|
|
188
|
+
</Button>,
|
|
189
|
+
);
|
|
190
|
+
const icon = screen.getByLabelText('check Icon');
|
|
191
|
+
expect(icon).toHaveClass('icon--xs');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('uses sm icon size for sm button', () => {
|
|
195
|
+
render(
|
|
196
|
+
<Button size="sm" iconLeft="check" ariaLabel="sm button">
|
|
197
|
+
Button
|
|
198
|
+
</Button>,
|
|
199
|
+
);
|
|
200
|
+
const icon = screen.getByLabelText('check Icon');
|
|
201
|
+
expect(icon).toHaveClass('icon--sm');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('uses md icon size for md button', () => {
|
|
205
|
+
render(
|
|
206
|
+
<Button size="md" iconLeft="check" ariaLabel="md button">
|
|
207
|
+
Button
|
|
208
|
+
</Button>,
|
|
209
|
+
);
|
|
210
|
+
const icon = screen.getByLabelText('check Icon');
|
|
211
|
+
expect(icon).toHaveClass('icon--md');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('uses md icon size for lg button', () => {
|
|
215
|
+
render(
|
|
216
|
+
<Button size="lg" iconLeft="check" ariaLabel="lg button">
|
|
217
|
+
Button
|
|
218
|
+
</Button>,
|
|
219
|
+
);
|
|
220
|
+
const icon = screen.getByLabelText('check Icon');
|
|
221
|
+
expect(icon).toHaveClass('icon--md');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('uses lg icon size for xl button', () => {
|
|
225
|
+
render(
|
|
226
|
+
<Button size="xl" iconLeft="check" ariaLabel="xl button">
|
|
227
|
+
Button
|
|
228
|
+
</Button>,
|
|
229
|
+
);
|
|
230
|
+
const icon = screen.getByLabelText('check Icon');
|
|
231
|
+
expect(icon).toHaveClass('icon--lg');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('applies correct icon size to loading icon based on button size', () => {
|
|
235
|
+
render(
|
|
236
|
+
<Button size="sm" isLoading ariaLabel="loading button">
|
|
237
|
+
Button
|
|
238
|
+
</Button>,
|
|
239
|
+
);
|
|
240
|
+
const loadingIcon = screen.getByLabelText('Loading...');
|
|
241
|
+
expect(loadingIcon).toHaveClass('icon--sm');
|
|
189
242
|
});
|
|
190
243
|
});
|
|
191
244
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type ButtonVariants = 'solid' | 'outline' | 'link' | 'action' | 'destructive' | 'soft';
|
|
2
2
|
|
|
3
|
-
export type ButtonSizes = 'xs' | 'sm' | 'md' | 'lg';
|
|
3
|
+
export type ButtonSizes = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
4
4
|
|
|
5
5
|
export type ButtonTypes = 'button' | 'submit' | 'reset';
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
border-color: transparent;
|
|
12
12
|
border-radius: var(--pf-button-border-radius);
|
|
13
13
|
font-weight: var(--pf-button-font-weight);
|
|
14
|
+
gap: 0; // Default gap, will be overridden by size-specific rules
|
|
14
15
|
|
|
15
16
|
cursor: pointer;
|
|
16
17
|
&:disabled {
|
|
@@ -20,27 +21,33 @@
|
|
|
20
21
|
|
|
21
22
|
// Button Sizes
|
|
22
23
|
.btn--xs {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
padding: var(--pf-spacing-xxs) var(--pf-spacing-sm);
|
|
25
|
+
font-size: var(--pf-font-size-sm);
|
|
26
|
+
gap: var(--pf-spacing-xs);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
.btn--sm {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
padding: var(--pf-spacing-xs) var(--pf-spacing-lg);
|
|
31
|
+
font-size: var(--pf-font-size-md);
|
|
32
|
+
gap: var(--pf-spacing-xs);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
.btn--md {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
padding: var(--pf-spacing-sm) var(--pf-spacing-2xl);
|
|
37
|
+
font-size: var(--pf-font-size-base);
|
|
38
|
+
gap: var(--pf-spacing-md);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
.btn--lg {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
padding: var(--pf-spacing-md) var(--pf-spacing-2xl);
|
|
43
|
+
font-size: var(--pf-font-size-2xl);
|
|
44
|
+
gap: var(--pf-spacing-md);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.btn--xl {
|
|
48
|
+
padding: var(--pf-spacing-lg) var(--pf-spacing-3xl);
|
|
49
|
+
font-size: var(--pf-font-size-3xl);
|
|
50
|
+
gap: var(--pf-spacing-md);
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
.btn--icon-only {
|
|
@@ -3,15 +3,8 @@
|
|
|
3
3
|
:root [data-theme='light'],
|
|
4
4
|
:root [data-theme='dark'] {
|
|
5
5
|
// Typography
|
|
6
|
-
|
|
7
|
-
--pf-button-font-
|
|
8
|
-
--pf-button-font-size-sm: calc(0.875 * var(--pf-font-size-base));
|
|
9
|
-
--pf-button-font-size-md: calc(0.875 * var(--pf-font-size-base));
|
|
10
|
-
--pf-button-font-size-lg: var(--pf-font-size-base);
|
|
11
|
-
--pf-button-line-height-xs: var(--pf-line-height-md);
|
|
12
|
-
--pf-button-line-height-sm: var(--pf-line-height-md);
|
|
13
|
-
--pf-button-line-height-md: var(--pf-line-height-md);
|
|
14
|
-
--pf-button-line-height-lg: var(--pf-line-height-lg);
|
|
6
|
+
|
|
7
|
+
--pf-button-font-weight: var(--pf-font-weight-medium);
|
|
15
8
|
|
|
16
9
|
// Border radii
|
|
17
10
|
--pf-button-border-radius: var(--pf-border-radius-md);
|
|
@@ -2,7 +2,7 @@ import { IconName } from '../../types';
|
|
|
2
2
|
|
|
3
3
|
export type ButtonVariants = 'solid' | 'outline' | 'link' | 'action' | 'destructive' | 'soft';
|
|
4
4
|
|
|
5
|
-
export type ButtonSizes = 'xs' | 'sm' | 'md' | 'lg';
|
|
5
|
+
export type ButtonSizes = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
6
6
|
|
|
7
7
|
export type ButtonTypes = 'button' | 'submit' | 'reset';
|
|
8
8
|
|
|
@@ -115,6 +115,7 @@ export const Uncontrolled: Story = {
|
|
|
115
115
|
data-testid="refresh-library"
|
|
116
116
|
ariaLabel="Refresh Data"
|
|
117
117
|
iconLeft="retrain"
|
|
118
|
+
size="sm"
|
|
118
119
|
onClick={() => console.log('Refresh Data')}
|
|
119
120
|
>
|
|
120
121
|
Refresh Data
|
|
@@ -123,6 +124,7 @@ export const Uncontrolled: Story = {
|
|
|
123
124
|
data-testid="configure-fields"
|
|
124
125
|
ariaLabel="Configure Fields"
|
|
125
126
|
iconLeft="edit"
|
|
127
|
+
size="sm"
|
|
126
128
|
onClick={() => console.log('Configure Fields')}
|
|
127
129
|
>
|
|
128
130
|
Configure Fields
|
|
@@ -131,6 +133,7 @@ export const Uncontrolled: Story = {
|
|
|
131
133
|
data-testid="delete-library"
|
|
132
134
|
ariaLabel="Delete Library"
|
|
133
135
|
iconLeft="trash"
|
|
136
|
+
size="sm"
|
|
134
137
|
onClick={() => console.log('Delete Library')}
|
|
135
138
|
>
|
|
136
139
|
Delete Library
|
|
@@ -198,6 +201,7 @@ export const Hover: Story = {
|
|
|
198
201
|
data-testid="hover-item-1"
|
|
199
202
|
ariaLabel="Item 1"
|
|
200
203
|
iconLeft="retrain"
|
|
204
|
+
size="sm"
|
|
201
205
|
onClick={() => console.log('Item 1')}
|
|
202
206
|
>
|
|
203
207
|
Item 1
|
|
@@ -206,6 +210,7 @@ export const Hover: Story = {
|
|
|
206
210
|
data-testid="hover-item-2"
|
|
207
211
|
ariaLabel="Item 2"
|
|
208
212
|
iconLeft="edit"
|
|
213
|
+
size="sm"
|
|
209
214
|
onClick={() => console.log('Item 2')}
|
|
210
215
|
>
|
|
211
216
|
Item 2
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
shift,
|
|
12
12
|
Placement,
|
|
13
13
|
useDismiss,
|
|
14
|
+
ReferenceType,
|
|
15
|
+
VirtualElement,
|
|
14
16
|
} from '@floating-ui/react';
|
|
15
17
|
import { FloatUIProps } from './types';
|
|
16
18
|
|
|
@@ -54,6 +56,8 @@ export function FloatUI({
|
|
|
54
56
|
throw new Error('Both children of FloatUI must be valid React elements.');
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
const referenceElement = floatingOptions.elements?.reference || referenceElementRef.current;
|
|
60
|
+
|
|
57
61
|
const { refs, floatingStyles, context } = useFloating({
|
|
58
62
|
...floatingOptions,
|
|
59
63
|
open: isOpen,
|
|
@@ -62,7 +66,7 @@ export function FloatUI({
|
|
|
62
66
|
onOpenChange?.(isOpen);
|
|
63
67
|
},
|
|
64
68
|
elements: {
|
|
65
|
-
reference:
|
|
69
|
+
reference: referenceElement as Element | null,
|
|
66
70
|
},
|
|
67
71
|
});
|
|
68
72
|
|
|
@@ -69,7 +69,12 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
69
69
|
<>
|
|
70
70
|
<div className="input-wrapper">
|
|
71
71
|
{iconName && (
|
|
72
|
-
<Icon
|
|
72
|
+
<Icon
|
|
73
|
+
name={iconName}
|
|
74
|
+
data-testid={`${name}-embedded-icon`}
|
|
75
|
+
className="embedded-icon"
|
|
76
|
+
size="sm"
|
|
77
|
+
/>
|
|
73
78
|
)}
|
|
74
79
|
<input
|
|
75
80
|
ref={ref}
|
|
@@ -95,7 +100,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
95
100
|
<Icon
|
|
96
101
|
name="x-close"
|
|
97
102
|
data-testid={`${name}-clearable-icon`}
|
|
98
|
-
size="
|
|
103
|
+
size="xs"
|
|
99
104
|
onClick={handleClear}
|
|
100
105
|
className="clearable-icon"
|
|
101
106
|
/>
|
|
@@ -16,13 +16,15 @@
|
|
|
16
16
|
position: relative;
|
|
17
17
|
.embedded-icon {
|
|
18
18
|
position: absolute;
|
|
19
|
-
top:
|
|
19
|
+
top: 50%;
|
|
20
|
+
transform: translateY(-50%);
|
|
20
21
|
left: var(--pf-spacing-sm);
|
|
21
22
|
color: var(--pf-semantic-font-regular);
|
|
22
23
|
}
|
|
23
24
|
.clearable-icon {
|
|
24
25
|
position: absolute;
|
|
25
|
-
top:
|
|
26
|
+
top: 50%;
|
|
27
|
+
transform: translateY(-50%);
|
|
26
28
|
right: var(--pf-spacing-sm);
|
|
27
29
|
color: var(--pf-semantic-font-regular);
|
|
28
30
|
cursor: pointer;
|
|
@@ -55,7 +55,12 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
55
55
|
<>
|
|
56
56
|
<div className="number-input-wrapper">
|
|
57
57
|
{iconName && (
|
|
58
|
-
<Icon
|
|
58
|
+
<Icon
|
|
59
|
+
name={iconName}
|
|
60
|
+
data-testid={`${name}-embedded-icon`}
|
|
61
|
+
className="embedded-icon"
|
|
62
|
+
size="sm"
|
|
63
|
+
/>
|
|
59
64
|
)}
|
|
60
65
|
<input
|
|
61
66
|
ref={ref}
|
|
@@ -78,7 +83,7 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
78
83
|
data-testid={`${name}-clearable-icon`}
|
|
79
84
|
onClick={handleClear}
|
|
80
85
|
className="clearable-icon"
|
|
81
|
-
size="
|
|
86
|
+
size="xs"
|
|
82
87
|
/>
|
|
83
88
|
)}
|
|
84
89
|
</div>
|
|
@@ -55,7 +55,7 @@ const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
|
|
|
55
55
|
return (
|
|
56
56
|
<>
|
|
57
57
|
<div className="password-input-wrapper">
|
|
58
|
-
<Icon name="lock" data-testid={`${name}-embedded-icon`} className="embedded-icon" />
|
|
58
|
+
<Icon name="lock" data-testid={`${name}-embedded-icon`} className="embedded-icon" size="sm" />
|
|
59
59
|
<input
|
|
60
60
|
ref={ref}
|
|
61
61
|
data-testid={`form-password-input-${name}`}
|
|
@@ -76,7 +76,7 @@ const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
|
|
|
76
76
|
<Icon
|
|
77
77
|
name={showPassword ? 'fa-eye-slash' : 'eye'}
|
|
78
78
|
data-testid={`${name}-${showPassword ? 'hide' : 'show'}-password-icon`}
|
|
79
|
-
size="
|
|
79
|
+
size="sm"
|
|
80
80
|
onClick={handleShowPassword}
|
|
81
81
|
className="toggle-show-password-icon"
|
|
82
82
|
/>
|
|
@@ -84,8 +84,8 @@ export const DefaultIcon: Story = {
|
|
|
84
84
|
|
|
85
85
|
export const IconStates: Story = {
|
|
86
86
|
render: (args) => {
|
|
87
|
-
const sizes: IconSizes[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'];
|
|
88
|
-
const rems = ['10px', '12px', '16px', '24px', '32px', '48px', '64px'];
|
|
87
|
+
const sizes: IconSizes[] = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'];
|
|
88
|
+
const rems = ['10px', '12px', '16px', '20px', '24px', '32px', '48px', '64px'];
|
|
89
89
|
|
|
90
90
|
return (
|
|
91
91
|
<Row>
|
|
@@ -3,7 +3,7 @@ import { PermafrostComponent } from '../../types';
|
|
|
3
3
|
import { IconName as FAIconName, IconPrefix } from '@fortawesome/fontawesome-svg-core';
|
|
4
4
|
import { indicons } from './indicons';
|
|
5
5
|
|
|
6
|
-
export type IconSizes = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl';
|
|
6
|
+
export type IconSizes = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl';
|
|
7
7
|
|
|
8
8
|
export type IndiconName = keyof typeof indicons;
|
|
9
9
|
|
|
@@ -41,6 +41,7 @@ export const Default: Story = {
|
|
|
41
41
|
data-testid="refresh-library"
|
|
42
42
|
ariaLabel="Refresh Data"
|
|
43
43
|
iconLeft="retrain"
|
|
44
|
+
size="sm"
|
|
44
45
|
onClick={() => console.log('Refresh Data')}
|
|
45
46
|
>
|
|
46
47
|
Refresh Data
|
|
@@ -49,6 +50,7 @@ export const Default: Story = {
|
|
|
49
50
|
data-testid="configure-fields"
|
|
50
51
|
ariaLabel="Configure Fields"
|
|
51
52
|
iconLeft="edit"
|
|
53
|
+
size="sm"
|
|
52
54
|
onClick={() => console.log('Configure Fields')}
|
|
53
55
|
>
|
|
54
56
|
Configure Fields
|
|
@@ -57,6 +59,7 @@ export const Default: Story = {
|
|
|
57
59
|
data-testid="delete-library"
|
|
58
60
|
ariaLabel="Delete Library"
|
|
59
61
|
iconLeft="trash"
|
|
62
|
+
size="sm"
|
|
60
63
|
onClick={() => console.log('Delete Library')}
|
|
61
64
|
>
|
|
62
65
|
Delete Library
|
|
@@ -64,6 +64,7 @@ export const Pagination = ({
|
|
|
64
64
|
variant="link"
|
|
65
65
|
onClick={handlePreviousPage}
|
|
66
66
|
iconLeft="chevron-left"
|
|
67
|
+
size="sm"
|
|
67
68
|
isDisabled={isPreviousButtonDisabled || totalPages === 0}
|
|
68
69
|
/>
|
|
69
70
|
</div>
|
|
@@ -107,6 +108,7 @@ export const Pagination = ({
|
|
|
107
108
|
onClick={handleNextPage}
|
|
108
109
|
iconLeft="chevron-right"
|
|
109
110
|
isDisabled={isNextButtonDisabled || totalPages === 0}
|
|
111
|
+
size="sm"
|
|
110
112
|
/>
|
|
111
113
|
</div>
|
|
112
114
|
</Col>
|
|
@@ -48,27 +48,27 @@ export const Default: Story = {
|
|
|
48
48
|
render: (args) => (
|
|
49
49
|
<Row>
|
|
50
50
|
<Col>
|
|
51
|
-
<Button ariaLabel="Click me" onClick={() => toast('Hello World')}>
|
|
51
|
+
<Button ariaLabel="Click me" onClick={() => toast('Hello World')} size="sm">
|
|
52
52
|
Fire default toast
|
|
53
53
|
</Button>
|
|
54
54
|
</Col>
|
|
55
55
|
<Col>
|
|
56
|
-
<Button ariaLabel="Click me" onClick={() => toast.info('Hello World')}>
|
|
56
|
+
<Button ariaLabel="Click me" onClick={() => toast.info('Hello World')} size="sm">
|
|
57
57
|
Fire info toast
|
|
58
58
|
</Button>
|
|
59
59
|
</Col>
|
|
60
60
|
<Col>
|
|
61
|
-
<Button ariaLabel="Click me" onClick={() => toast.warning('Hello World')}>
|
|
61
|
+
<Button ariaLabel="Click me" onClick={() => toast.warning('Hello World')} size="sm">
|
|
62
62
|
Fire warning toast
|
|
63
63
|
</Button>
|
|
64
64
|
</Col>
|
|
65
65
|
<Col>
|
|
66
|
-
<Button ariaLabel="Click me" onClick={() => toast.error('Hello World')}>
|
|
66
|
+
<Button ariaLabel="Click me" onClick={() => toast.error('Hello World')} size="sm">
|
|
67
67
|
Fire error toast
|
|
68
68
|
</Button>
|
|
69
69
|
</Col>
|
|
70
70
|
<Col>
|
|
71
|
-
<Button ariaLabel="Click me" onClick={() => toast.success('Hello World')}>
|
|
71
|
+
<Button ariaLabel="Click me" onClick={() => toast.success('Hello World')} size="sm">
|
|
72
72
|
Fire success toast
|
|
73
73
|
</Button>
|
|
74
74
|
<ToastContainer {...args} />
|