@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.
@@ -1,4 +1,4 @@
1
- import '@aarhus-university/au-designsystem-delphinus-dev/build/style.css';
1
+ import '@aarhus-university/au-designsystem-delphinus/build/style.css';
2
2
  import { globalTheme, globalLang } from '../stories/lib/helpers';
3
3
 
4
4
  export const parameters = {
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": "10.21.1",
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.33.2",
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.13.8",
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 = 'button--icon';
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 | null = null;
21
+ let cleanUp: CleanUpPair[];
19
22
  if (toggle) {
20
- cleanUp = setContentToggle(toggle, beforeCallback, afterCallback);
21
- cleanUp?.element.setAttribute('aria-expanded', toggled ? 'true' : 'false');
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
- if (cleanUp) {
26
- cleanUp.element.removeEventListener('click', cleanUp.clickEvent);
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 { setToolbars, setToolbarToggle } from '@aarhus-university/au-designsystem-delphinus/source/js/components/toolbar';
5
-
6
- let windowInnerWidth = 0;
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
- setToolbars();
14
- const cleanUp: CleanUpPair[] = setToolbarToggle();
15
- windowInnerWidth = window.innerWidth;
16
- window.addEventListener('resize', () => {
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
- cleanUp.forEach((pair: CleanUpPair) => {
26
- pair.element.removeEventListener('click', pair.clickEvent);
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 className="toolbar">
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="toolbar__toggle"
42
- title={label}
72
+ className={buttonClass}
73
+ title={title || label}
74
+ aria-label={title}
43
75
  aria-haspopup="true"
44
76
  >
45
- {label}
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({});