@aarhus-university/au-lib-react-components 10.21.1 → 11.0.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/.storybook/preview.js +1 -1
- package/package.json +3 -3
- package/src/components/AUButtonComponent.tsx +1 -2
- package/src/components/AUContentToggleComponent.tsx +11 -6
- package/src/components/AUToolbarComponent.tsx +67 -18
- package/src/components/AUTruncatorComponent.tsx +129 -0
- package/stories/AUButtonComponent.stories.tsx +10 -4
package/.storybook/preview.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sideEffects": false,
|
|
3
3
|
"name": "@aarhus-university/au-lib-react-components",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "11.0.0",
|
|
5
5
|
"description": "Library for shared React components for various applications on au.dk",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "jest",
|
|
@@ -72,9 +72,9 @@
|
|
|
72
72
|
"webpack-cli": "^4.9.2"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@aarhus-university/au-designsystem-delphinus": "0.
|
|
75
|
+
"@aarhus-university/au-designsystem-delphinus": "0.34.2",
|
|
76
76
|
"@aarhus-university/au-designsystem-delphinus-dev": "0.2.0",
|
|
77
|
-
"@aarhus-university/types": "0.
|
|
77
|
+
"@aarhus-university/types": "^0.16.1",
|
|
78
78
|
"@reduxjs/toolkit": "^1.8.3",
|
|
79
79
|
"@types/google.analytics": "^0.0.42",
|
|
80
80
|
"@types/history": "^5.0.0",
|
|
@@ -21,7 +21,7 @@ const AUButtonComponent: FC<AUButtonComponentProps> = ({
|
|
|
21
21
|
const typeClass = type === 'default' ? '' : `button--${type}`;
|
|
22
22
|
let iconClass = '';
|
|
23
23
|
if (icon) {
|
|
24
|
-
iconClass =
|
|
24
|
+
iconClass = `button--icon icon-${icon}`;
|
|
25
25
|
if (iconPosition) {
|
|
26
26
|
iconClass = `${iconClass} button--icon--${iconPosition}`;
|
|
27
27
|
}
|
|
@@ -48,7 +48,6 @@ const AUButtonComponent: FC<AUButtonComponentProps> = ({
|
|
|
48
48
|
modeClass,
|
|
49
49
|
disabled && mode !== 'processing' ? 'visually-disabled' : '',
|
|
50
50
|
].concat(classNames || []).join(' ').replace(/\s+/g, ' ').trim()}
|
|
51
|
-
data-icon={icon === '' ? null : icon}
|
|
52
51
|
title={icon && hideLabel ? label : undefined}
|
|
53
52
|
aria-expanded={ariaExpanded}
|
|
54
53
|
onClick={(event: MouseEvent<HTMLButtonElement>) => {
|
|
@@ -3,28 +3,33 @@ import React, {
|
|
|
3
3
|
useRef,
|
|
4
4
|
FC,
|
|
5
5
|
} from 'react';
|
|
6
|
+
// import { setContentToggle } from '@aarhus-university/au-desi
|
|
7
|
+
// gnsystem-delphinus/source/js/components/content-toggle';
|
|
6
8
|
import { setContentToggle } from '@aarhus-university/au-designsystem-delphinus/source/js/components/content-toggle';
|
|
7
9
|
|
|
8
10
|
const AUContentToggleComponent: FC<AUContentToggleComponentProps> = ({
|
|
9
11
|
toggled,
|
|
10
12
|
classNames,
|
|
11
13
|
children,
|
|
14
|
+
onClick,
|
|
12
15
|
beforeCallback,
|
|
13
16
|
afterCallback,
|
|
14
17
|
}: AUContentToggleComponentProps) => {
|
|
15
18
|
const toggleContainer = useRef<HTMLDivElement>(null);
|
|
16
19
|
useEffect(() => {
|
|
17
20
|
const toggle = toggleContainer?.current?.querySelector('.content-toggle__content');
|
|
18
|
-
let cleanUp: CleanUpPair
|
|
21
|
+
let cleanUp: CleanUpPair[];
|
|
19
22
|
if (toggle) {
|
|
20
|
-
cleanUp = setContentToggle(toggle, beforeCallback, afterCallback);
|
|
21
|
-
cleanUp
|
|
23
|
+
cleanUp = setContentToggle(toggle, onClick, beforeCallback, afterCallback);
|
|
24
|
+
if (cleanUp.length > 0) { // Den første er altid knappen (og ikke luk-knappen)
|
|
25
|
+
cleanUp[0].element.setAttribute('aria-expanded', toggled ? 'true' : 'false');
|
|
26
|
+
}
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
return () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
30
|
+
cleanUp.forEach((c) => {
|
|
31
|
+
c.element.removeEventListener('click', c.clickEvent);
|
|
32
|
+
});
|
|
28
33
|
};
|
|
29
34
|
}, []);
|
|
30
35
|
return (
|
|
@@ -1,52 +1,101 @@
|
|
|
1
1
|
/* eslint-disable react/no-array-index-key */
|
|
2
2
|
/* eslint-env browser */
|
|
3
|
-
import React, { useEffect, FC } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import React, { useEffect, FC, useRef } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
setToolbar, closeAllContentTogglesInToolbar, ExtendedCleanUpPair, setOverflow,
|
|
6
|
+
} from '@aarhus-university/au-designsystem-delphinus/source/js/components/toolbar';
|
|
7
7
|
|
|
8
8
|
const AUToolbarComponent: FC<AUToolbarComponentProps> = ({
|
|
9
9
|
lang,
|
|
10
10
|
elements,
|
|
11
|
+
noCollapse,
|
|
12
|
+
center,
|
|
13
|
+
title,
|
|
14
|
+
buttonClass,
|
|
15
|
+
buttonElements,
|
|
11
16
|
}: AUToolbarComponentProps) => {
|
|
17
|
+
const toolbarRef = useRef<HTMLDivElement>(null);
|
|
12
18
|
useEffect(() => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (windowInnerWidth > 0 && window.innerWidth !== windowInnerWidth) {
|
|
18
|
-
windowInnerWidth = window.innerWidth;
|
|
19
|
-
setToolbars();
|
|
19
|
+
const onResize = () => {
|
|
20
|
+
if (toolbarRef.current?.querySelector('.toolbar__items')) {
|
|
21
|
+
closeAllContentTogglesInToolbar(toolbarRef.current);
|
|
22
|
+
setOverflow(toolbarRef.current);
|
|
20
23
|
}
|
|
21
|
-
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let toolbarCleanUp: ExtendedCleanUpPair[];
|
|
27
|
+
if (toolbarRef.current) {
|
|
28
|
+
// eslint-disable-next-line max-len
|
|
29
|
+
toolbarCleanUp = setToolbar(toolbarRef.current, (toolbar: HTMLElement) => {
|
|
30
|
+
closeAllContentTogglesInToolbar(toolbar);
|
|
31
|
+
}, false);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
window.addEventListener('resize', onResize);
|
|
22
35
|
|
|
23
36
|
// Runs on unmounting
|
|
24
37
|
return () => {
|
|
25
|
-
|
|
26
|
-
|
|
38
|
+
toolbarCleanUp.forEach((pair: ExtendedCleanUpPair) => {
|
|
39
|
+
if (typeof pair.clickEvent === 'function') {
|
|
40
|
+
pair.element.removeEventListener('click', pair.clickEvent);
|
|
41
|
+
}
|
|
42
|
+
if (typeof pair.keyUpEvent === 'function') {
|
|
43
|
+
pair.element.removeEventListener('keyup', pair.keyUpEvent);
|
|
44
|
+
}
|
|
27
45
|
});
|
|
46
|
+
|
|
47
|
+
window.removeEventListener('resize', onResize);
|
|
28
48
|
};
|
|
29
49
|
}, []);
|
|
30
50
|
|
|
31
51
|
const renderElements = elements.map((e, i) => <React.Fragment key={i}>{e}</React.Fragment>);
|
|
32
52
|
const label = lang === 'da' ? 'Muligheder' : 'Options';
|
|
33
53
|
|
|
54
|
+
let toolbarClass = 'toolbar';
|
|
55
|
+
if (noCollapse) {
|
|
56
|
+
toolbarClass = `${toolbarClass} toolbar--no-collapse`;
|
|
57
|
+
}
|
|
58
|
+
if (center) {
|
|
59
|
+
toolbarClass = `${toolbarClass} toolbar--center-align-items`;
|
|
60
|
+
}
|
|
61
|
+
|
|
34
62
|
return (
|
|
35
|
-
<div
|
|
63
|
+
<div
|
|
64
|
+
ref={toolbarRef}
|
|
65
|
+
className={toolbarClass}
|
|
66
|
+
>
|
|
36
67
|
<div className="toolbar__items">
|
|
37
68
|
{renderElements}
|
|
38
69
|
</div>
|
|
39
70
|
<button
|
|
40
71
|
type="button"
|
|
41
|
-
className=
|
|
42
|
-
title={label}
|
|
72
|
+
className={buttonClass}
|
|
73
|
+
title={title || label}
|
|
74
|
+
aria-label={title}
|
|
43
75
|
aria-haspopup="true"
|
|
44
76
|
>
|
|
45
|
-
{
|
|
77
|
+
{
|
|
78
|
+
buttonElements?.length === 0 && (title || label)
|
|
79
|
+
}
|
|
80
|
+
{
|
|
81
|
+
(buttonElements || []).map((element, index) => (
|
|
82
|
+
<React.Fragment key={index}>
|
|
83
|
+
{element}
|
|
84
|
+
</React.Fragment>
|
|
85
|
+
))
|
|
86
|
+
}
|
|
46
87
|
</button>
|
|
47
88
|
</div>
|
|
48
89
|
);
|
|
49
90
|
};
|
|
50
91
|
|
|
92
|
+
AUToolbarComponent.defaultProps = {
|
|
93
|
+
noCollapse: false,
|
|
94
|
+
center: false,
|
|
95
|
+
title: undefined,
|
|
96
|
+
buttonClass: 'toolbar__toggle',
|
|
97
|
+
buttonElements: [],
|
|
98
|
+
};
|
|
99
|
+
|
|
51
100
|
AUToolbarComponent.displayName = 'AUToolbarComponent';
|
|
52
101
|
export default AUToolbarComponent;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
FC, useEffect, useRef, useState,
|
|
3
|
+
} from 'react';
|
|
4
|
+
import { overflowsY } from '@aarhus-university/au-designsystem-delphinus/source/js/components/helpers';
|
|
5
|
+
|
|
6
|
+
const focusableSelector = 'a[href]:not([tabindex="-1"]), area[href]:not([tabindex="-1"]), input:not([disabled]):not([tabindex="-1"]), select:not([disabled]):not([tabindex="-1"]), textarea:not([disabled]):not([tabindex="-1"]), button:not([disabled]):not([tabindex="-1"]), iframe:not([tabindex="-1"]), [tabindex]:not([tabindex="-1"]), [contentEditable=true]:not([tabindex="-1"])';
|
|
7
|
+
const getMaxLinesStyle = (maxLines: number): React.CSSProperties => ({ '--max-lines': maxLines } as React.CSSProperties);
|
|
8
|
+
|
|
9
|
+
const setTruncate = (
|
|
10
|
+
expandElement: HTMLElement,
|
|
11
|
+
contentElement: HTMLElement,
|
|
12
|
+
expanded: boolean,
|
|
13
|
+
): void => {
|
|
14
|
+
if (!expanded && overflowsY(contentElement)) {
|
|
15
|
+
expandElement.removeAttribute('hidden');
|
|
16
|
+
} else {
|
|
17
|
+
expandElement.parentElement?.classList.add('truncator--no-truncation');
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const AUTruncatorComponent: FC<AUTruncatorComponentProps> = ({
|
|
22
|
+
maxLines,
|
|
23
|
+
header,
|
|
24
|
+
headerLevel,
|
|
25
|
+
children,
|
|
26
|
+
classNames,
|
|
27
|
+
buttonChild,
|
|
28
|
+
}: AUTruncatorComponentProps) => {
|
|
29
|
+
const expandRef = useRef<HTMLButtonElement>(null);
|
|
30
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
31
|
+
const [expanded, setExpanded] = useState<boolean>(false);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setTruncate(expandRef.current as HTMLElement, contentRef.current as HTMLElement, expanded);
|
|
34
|
+
const focusElements = contentRef.current?.querySelectorAll(focusableSelector);
|
|
35
|
+
|
|
36
|
+
const onFocus = (event: Event) => {
|
|
37
|
+
if (overflowsY(event.target as HTMLElement)) {
|
|
38
|
+
setExpanded(true);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (focusElements) {
|
|
43
|
+
focusElements.forEach((f) => {
|
|
44
|
+
f.addEventListener('focus', onFocus);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const onResize = () => {
|
|
49
|
+
setTruncate(expandRef.current as HTMLElement, contentRef.current as HTMLElement, expanded);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
window.addEventListener('resize', onResize);
|
|
53
|
+
|
|
54
|
+
return () => {
|
|
55
|
+
if (focusElements) {
|
|
56
|
+
focusElements.forEach((f) => {
|
|
57
|
+
f.removeEventListener('focus', onFocus);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
window.removeEventListener('resize', onResize);
|
|
61
|
+
};
|
|
62
|
+
}, [expanded]);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className={`${classNames}${expanded ? ' truncator--no-truncation' : ''}`} style={getMaxLinesStyle(maxLines)}>
|
|
66
|
+
{
|
|
67
|
+
header && (
|
|
68
|
+
<>
|
|
69
|
+
{
|
|
70
|
+
headerLevel === 1 && (
|
|
71
|
+
<h1 className="truncator__header">{header}</h1>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
{
|
|
75
|
+
headerLevel === 2 && (
|
|
76
|
+
<h2 className="truncator__header">{header}</h2>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
{
|
|
80
|
+
headerLevel === 3 && (
|
|
81
|
+
<h3 className="truncator__header">{header}</h3>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
{
|
|
85
|
+
headerLevel === 4 && (
|
|
86
|
+
<h4 className="truncator__header">{header}</h4>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
{
|
|
90
|
+
headerLevel === 5 && (
|
|
91
|
+
<h5 className="truncator__header">{header}</h5>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
{
|
|
95
|
+
headerLevel === 6 && (
|
|
96
|
+
<h6 className="truncator__header">{header}</h6>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
</>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
<button
|
|
103
|
+
ref={expandRef}
|
|
104
|
+
type="button"
|
|
105
|
+
className="truncator__expand"
|
|
106
|
+
hidden
|
|
107
|
+
aria-expanded={expanded}
|
|
108
|
+
onClick={() => setExpanded(true)}
|
|
109
|
+
>
|
|
110
|
+
{buttonChild}
|
|
111
|
+
</button>
|
|
112
|
+
<div
|
|
113
|
+
ref={contentRef}
|
|
114
|
+
className="truncator__content"
|
|
115
|
+
>
|
|
116
|
+
{children}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
AUTruncatorComponent.defaultProps = {
|
|
123
|
+
header: undefined,
|
|
124
|
+
headerLevel: 2,
|
|
125
|
+
classNames: 'truncator',
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
AUTruncatorComponent.displayName = 'AUTruncatorComponent';
|
|
129
|
+
export default AUTruncatorComponent;
|
|
@@ -27,6 +27,12 @@ export default {
|
|
|
27
27
|
'large',
|
|
28
28
|
]
|
|
29
29
|
},
|
|
30
|
+
icon: {
|
|
31
|
+
control: {
|
|
32
|
+
type: 'select',
|
|
33
|
+
},
|
|
34
|
+
options: ['add', 'attachment', 'award', 'bike', 'bookmark', 'brand-linkedin', 'brand-mendeley', 'brand-orcid', 'brand-researchgate', 'brand-twitter', 'calendar', 'camera', 'car', 'class', 'close', 'collapse', 'confirm', 'confirm-circle', 'context-menu', 'copy', 'current-semester', 'dark-mode', 'delphinus', 'delete', 'design', 'download', 'edit', 'email', 'exam', 'expand', 'failed', 'failed-circle', 'file', 'filter', 'finish', 'heart', 'hide', 'infinity', 'info', 'info-circle', 'instructor', 'instructor-with-class', 'language', 'location', 'link-chain', 'link-internal', 'link-external', 'link-external-square', 'lms-course-common', 'lms-course-sections', 'lms-course-additional', 'lms-course-merge-with', 'lms-course-merged', 'mailbox', 'menu', 'news', 'next', 'note', 'notifications', 'passed-exemption', 'phone', 'phone-landline', 'previous', 'print', 'public-transport', 'receipt', 'refresh', 'reorder', 'replay', 'route', 'search', 'season-autumn', 'season-christmas', 'season-easter', 'season-halloween', 'season-spring', 'season-summer', 'season-winter', 'send', 'settings', 'source-system', 'student-place-accepted', 'student-place-not-offered', 'student-place-offered', 'student-place-rejected', 'student-place-standby', 'sign-in', 'sign-out', 'spock', 'sync', 'theme-chooser', 'tip', 'tools', 'tooltip', 'upload', 'user-profile', 'user-impersonation', 'view', 'viewport-default', 'viewport-medium', 'viewport-wide', 'walking', 'warning', 'warning-triangle'],
|
|
35
|
+
},
|
|
30
36
|
iconPosition: {
|
|
31
37
|
control: {
|
|
32
38
|
type: 'select',
|
|
@@ -78,7 +84,7 @@ With_Icon.args = {
|
|
|
78
84
|
type: 'default',
|
|
79
85
|
size: 'default',
|
|
80
86
|
mode: 'none',
|
|
81
|
-
icon: '
|
|
87
|
+
icon: 'delphinus',
|
|
82
88
|
iconPosition: 'left',
|
|
83
89
|
hideLabel: false,
|
|
84
90
|
};
|
|
@@ -89,7 +95,7 @@ Icon_Only.args = {
|
|
|
89
95
|
type: 'default',
|
|
90
96
|
size: 'default',
|
|
91
97
|
mode: 'none',
|
|
92
|
-
icon: '
|
|
98
|
+
icon: 'delphinus',
|
|
93
99
|
iconPosition: 'left',
|
|
94
100
|
hideLabel: true,
|
|
95
101
|
};
|
|
@@ -100,7 +106,7 @@ Irreversible_Action.args = {
|
|
|
100
106
|
type: 'default',
|
|
101
107
|
size: 'default',
|
|
102
108
|
mode: 'ireversable-action',
|
|
103
|
-
icon: '
|
|
109
|
+
icon: 'delphinus',
|
|
104
110
|
};
|
|
105
111
|
|
|
106
112
|
export const Confirmable_Action = Template.bind({});
|
|
@@ -109,7 +115,7 @@ Confirmable_Action.args = {
|
|
|
109
115
|
type: 'default',
|
|
110
116
|
size: 'default',
|
|
111
117
|
mode: 'confirmable-action',
|
|
112
|
-
icon: '
|
|
118
|
+
icon: 'delphinus',
|
|
113
119
|
};
|
|
114
120
|
|
|
115
121
|
export const Processing = Template.bind({});
|