@aarhus-university/au-lib-react-components 10.5.0 → 10.8.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,16 +1,6 @@
1
- import React, { FC, MouseEvent } from 'react';
2
-
3
- interface AUButtonComponentProps {
4
- label: string,
5
- disabled?: boolean,
6
- size?: 'small' | 'default' | 'large',
7
- type?: 'default' | 'text' | 'dimmed';
8
- icon?: string, // Maybe a list of available font awesome glyphs?
9
- iconPosition?: 'left' | 'right',
10
- hideLabel?: boolean,
11
- mode?: 'none' | 'ireversable-action' | 'confirmable-action' | 'processing', // (sic)
12
- onClick?: (event: MouseEvent<HTMLButtonElement>) => void,
13
- }
1
+ import React, {
2
+ FC, MouseEvent, RefObject,
3
+ } from 'react';
14
4
 
15
5
  const AUButtonComponent: FC<AUButtonComponentProps> = ({
16
6
  label,
@@ -21,6 +11,9 @@ const AUButtonComponent: FC<AUButtonComponentProps> = ({
21
11
  iconPosition,
22
12
  hideLabel,
23
13
  mode,
14
+ btnRef,
15
+ classNames,
16
+ ariaExpanded,
24
17
  onClick,
25
18
  }: AUButtonComponentProps) => {
26
19
  const sizeClass = size === 'default' ? '' : `button--${size}`;
@@ -37,11 +30,13 @@ const AUButtonComponent: FC<AUButtonComponentProps> = ({
37
30
  }
38
31
  }
39
32
  let modeClass = '';
40
- if (mode) {
33
+ if (mode !== 'none') {
41
34
  modeClass = `button--${mode}`;
42
35
  }
36
+
43
37
  return (
44
38
  <button
39
+ ref={btnRef}
45
40
  type="button"
46
41
  disabled={disabled}
47
42
  className={[
@@ -51,10 +46,15 @@ const AUButtonComponent: FC<AUButtonComponentProps> = ({
51
46
  iconClass,
52
47
  modeClass,
53
48
  disabled && mode !== 'processing' ? 'visually-disabled' : '',
54
- ].join(' ').replace(/\s+/g, ' ').trim()}
49
+ ].concat(classNames || []).join(' ').replace(/\s+/g, ' ').trim()}
55
50
  data-icon={icon === '' ? null : icon}
56
51
  title={icon && hideLabel ? label : undefined}
57
- onClick={onClick}
52
+ aria-expanded={ariaExpanded}
53
+ onClick={(event: MouseEvent<HTMLButtonElement>) => {
54
+ if (typeof onClick !== 'undefined') {
55
+ onClick(event, btnRef);
56
+ }
57
+ }}
58
58
  >
59
59
  {label}
60
60
  </button>
@@ -69,8 +69,14 @@ AUButtonComponent.defaultProps = {
69
69
  iconPosition: 'left',
70
70
  hideLabel: false,
71
71
  mode: 'none',
72
+ classNames: [],
73
+ ariaExpanded: false,
72
74
  // eslint-disable-next-line no-console
73
- onClick: (event: MouseEvent<HTMLButtonElement>) => console.log(event),
75
+ onClick: (
76
+ event: MouseEvent<HTMLButtonElement>,
77
+ ref: RefObject<HTMLButtonElement> | undefined,
78
+ // eslint-disable-next-line no-console
79
+ ) => console.log(event, ref),
74
80
  };
75
81
 
76
82
  AUButtonComponent.displayName = 'AUButtonComponent';
@@ -2,63 +2,46 @@
2
2
  /* eslint jsx-a11y/click-events-have-key-events: 0 */
3
3
  /* eslint jsx-a11y/no-noninteractive-element-interactions: 0 */
4
4
  /* eslint jsx-a11y/no-static-element-interactions: 0 */
5
- import React, { FC, useLayoutEffect } from 'react';
5
+ import React, { FC } from 'react';
6
6
  import ReactDOM from 'react-dom';
7
- import { hideModal } from '@aarhus-university/au-designsystem-delphinus/source/js/components/modal-view';
8
-
9
- export {
10
- hideModal,
11
- };
7
+ import AUButtonComponent from './AUButtonComponent';
8
+ import { useModal } from '../lib/hooks';
12
9
 
13
10
  const AUModalComponent: FC<AUModalComponentProps> = ({
11
+ lang,
14
12
  domId,
13
+ className,
15
14
  children,
16
- onClose,
17
15
  closeButton,
18
16
  closeButtonDisabled,
19
- lang,
20
17
  sender,
21
- className,
18
+ show,
19
+ onClose,
22
20
  }: AUModalComponentProps) => {
23
- useLayoutEffect(() => {
24
- const modalView = document.getElementById(domId);
25
- if (modalView) {
26
- const header = modalView.querySelector('.modal-view__header');
27
- if (header && !header.getAttribute('id')) {
28
- header.setAttribute('id', `${domId}-header`);
29
- modalView.setAttribute('aria-labelledby', `${domId}-header`);
30
- }
31
- }
32
- });
21
+ useModal(domId, sender, show, !closeButtonDisabled, onClose);
33
22
 
34
23
  return (
35
24
  ReactDOM.createPortal(
36
25
  <div
37
- key={0}
38
26
  id={domId}
39
27
  className={className}
40
28
  >
41
29
  <div className="modal-view__wrapper theme--normal">
42
30
  {
43
31
  closeButton && (
44
- <button
45
- type="button"
32
+ <AUButtonComponent
33
+ label={lang === 'da' ? 'Luk' : 'Close'}
46
34
  disabled={closeButtonDisabled}
47
- className="modal-view__close button button--icon button--icon--hide-label"
48
- data-icon=""
49
- data-gtm="au-btn-modal-close"
50
- title={lang === 'da' ? 'Luk' : 'Close'}
35
+ icon=""
36
+ hideLabel
37
+ classNames={['modal-view__close']}
38
+ dataGtm="au-btn-modal-close"
51
39
  onClick={() => {
52
40
  if (typeof onClose === 'function') {
53
41
  onClose();
54
42
  }
55
- if (sender) {
56
- hideModal(domId, sender);
57
- }
58
43
  }}
59
- >
60
- {lang === 'da' ? 'Luk' : 'Close'}
61
- </button>
44
+ />
62
45
  )
63
46
  }
64
47
  {children}
@@ -71,6 +54,7 @@ const AUModalComponent: FC<AUModalComponentProps> = ({
71
54
  };
72
55
 
73
56
  AUModalComponent.defaultProps = {
57
+ domId: 'modal-view1',
74
58
  closeButton: true,
75
59
  lang: 'da',
76
60
  className: 'modal-view',
@@ -0,0 +1,42 @@
1
+ import React, { FC } from 'react';
2
+
3
+ const AUNotificationsComponent: FC<AUNotificationsComponentProps> = ({
4
+ header,
5
+ content,
6
+ actions,
7
+ type,
8
+ classNames,
9
+ }: AUNotificationsComponentProps) => {
10
+ const typeClass = type === 'default' ? '' : `notification--${type}`;
11
+ return (
12
+ <div className={[
13
+ 'notification',
14
+ typeClass,
15
+ ].concat(classNames || []).join(' ').replace(/\s+/g, ' ').trim()}
16
+ >
17
+ <div className="notification__content">
18
+ {
19
+ header && <h2 className="notification__header">{header}</h2>
20
+ }
21
+ {content.map((c) => c)}
22
+ </div>
23
+ {
24
+ (actions || []).length > 0 && (
25
+ <div className="notification__actions">
26
+ {(actions || []).map((a) => a)}
27
+ </div>
28
+ )
29
+ }
30
+ </div>
31
+ );
32
+ };
33
+
34
+ AUNotificationsComponent.defaultProps = {
35
+ header: null,
36
+ actions: [],
37
+ type: 'default',
38
+ classNames: [],
39
+ };
40
+
41
+ AUNotificationsComponent.displayName = 'AUNotificationsComponent';
42
+ export default AUNotificationsComponent;
@@ -1,91 +1,33 @@
1
1
  /* eslint-disable react/static-property-placement */
2
2
  /* eslint-env browser */
3
- import React from 'react';
4
- import { isElementInViewport } from '../lib/helpers';
5
-
6
- type AUSpinnerComponentState = {
7
- loading: boolean,
8
- }
9
-
10
- class AUSpinnerComponent extends React.Component<AUSpinnerComponentProps, AUSpinnerComponentState> {
11
- static displayName = 'AUSpinnerComponent';
12
-
13
- static defaultProps = {
14
- domID: 'au-spinner-component',
15
- visible: false,
16
- className: '',
17
- height: 'auto',
18
- init: '',
19
- };
20
-
21
- constructor(props: AUSpinnerComponentProps) {
22
- super(props);
23
-
24
- this.state = {
25
- loading: true,
26
- };
27
-
28
- this.lazyLoad = this.lazyLoad.bind(this);
29
- }
30
-
31
- componentDidMount() {
32
- this.lazyLoad();
33
- window.addEventListener('scroll', () => {
34
- this.lazyLoad();
35
- });
36
- }
37
-
38
- componentDidUpdate() {
39
- this.lazyLoad();
40
- }
41
-
42
- lazyLoad() {
43
- const {
44
- loadingCondition,
45
- loaded,
46
- visible,
47
- domID,
48
- onLoad,
49
- } = this.props;
50
- const { loading } = this.state;
51
-
52
- const element = document.getElementById(domID || '');
53
- if (element
54
- && !loaded
55
- && loading
56
- && loadingCondition
57
- && (visible || isElementInViewport(element))) {
58
- this.setState({
59
- loading: false,
60
- }, () => {
61
- onLoad();
62
- });
63
- }
64
- }
65
-
66
- render() {
67
- const {
68
- loaded, domID, children, init, className, height,
69
- } = this.props;
70
-
71
- if (!loaded) {
72
- if (init === 'table') {
73
- return (
74
- <div id={domID} className={`processing-state processing-state--init-table ${className}`} style={{ minHeight: height }} />
75
- );
76
- }
77
- if (init === 'box') {
78
- return (
79
- <div id={domID} className={`processing-state processing-state--init-box ${className}`} style={{ minHeight: height }} />
80
- );
81
- }
3
+ import React, { FC } from 'react';
4
+
5
+ const AUSpinnerComponent: FC<AUSpinnerComponentProps> = ({
6
+ loaded,
7
+ init,
8
+ height,
9
+ className,
10
+ children,
11
+ }: AUSpinnerComponentProps) => {
12
+ if (!loaded) {
13
+ if (init === 'table' || init === 'box') {
82
14
  return (
83
- <div id={domID} className={`processing-state ${className}`} style={{ minHeight: height }} />
15
+ <div
16
+ className={`processing-state processing-state--init-${init} ${className}`}
17
+ style={{ minHeight: height }}
18
+ />
84
19
  );
85
20
  }
86
-
87
- return children;
21
+ return (
22
+ <div
23
+ className={`processing-state ${className}`}
24
+ style={{ minHeight: height }}
25
+ />
26
+ );
88
27
  }
89
- }
90
28
 
29
+ return children;
30
+ };
31
+
32
+ AUSpinnerComponent.displayName = 'AUSpinnerComponent';
91
33
  export default AUSpinnerComponent;
@@ -11,7 +11,7 @@ const AUToolbarComponent: FC<AUToolbarComponentProps> = ({
11
11
  }: AUToolbarComponentProps) => {
12
12
  useEffect(() => {
13
13
  setToolbars();
14
- setToolbarToggle();
14
+ const cleanUp: CleanUpPair[] = setToolbarToggle();
15
15
  windowInnerWidth = window.innerWidth;
16
16
  window.addEventListener('resize', () => {
17
17
  if (windowInnerWidth > 0 && window.innerWidth !== windowInnerWidth) {
@@ -19,6 +19,13 @@ const AUToolbarComponent: FC<AUToolbarComponentProps> = ({
19
19
  setToolbars();
20
20
  }
21
21
  });
22
+
23
+ // Runs on unmounting
24
+ return () => {
25
+ cleanUp.forEach((pair: CleanUpPair) => {
26
+ pair.element.removeEventListener('click', pair.clickEvent);
27
+ });
28
+ };
22
29
  }, []);
23
30
 
24
31
  const renderElements = elements.map((e, i) => <React.Fragment key={i}>{e}</React.Fragment>);
package/src/lib/hooks.ts CHANGED
@@ -1,31 +1,35 @@
1
+ /* eslint-disable @typescript-eslint/no-empty-function */
1
2
  /* eslint-disable import/prefer-default-export */
2
- import { useState, useEffect } from 'react';
3
+ import { useEffect } from 'react';
3
4
  import { showModal, hideModal } from '@aarhus-university/au-designsystem-delphinus/source/js/components/modal-view';
4
5
 
5
6
  const useModal = (
6
7
  domId: string,
7
- initialState: boolean,
8
- showCondition = true,
9
- hideCondition = false,
8
+ sender: HTMLButtonElement | null,
9
+ show = true,
10
10
  closeable = true,
11
- ): [IModal, (modal: IModal) => void] => {
12
- const [modal, setModal] = useState<IModal>({ modal: initialState, sender: null });
13
-
11
+ onClose: () => void = () => { },
12
+ ): void => {
14
13
  useEffect(() => {
15
- if (modal.modal && modal.sender && showCondition) {
16
- showModal(domId, modal.sender, () => {
17
- setModal({ modal: false, sender: null });
14
+ const modalView = document.getElementById(domId);
15
+ if (modalView) {
16
+ const header = modalView.querySelector('.modal-view__header');
17
+ if (header && !header.getAttribute('id')) {
18
+ header.setAttribute('id', `${domId}-header`);
19
+ modalView.setAttribute('aria-labelledby', `${domId}-header`);
20
+ }
21
+ }
22
+
23
+ if (sender && show) {
24
+ showModal(domId, sender, () => {
25
+ onClose();
18
26
  }, closeable);
19
27
  }
20
28
 
21
- if (modal.modal && modal.sender && hideCondition) {
22
- hideModal(domId, modal.sender, () => {
23
- setModal({ modal: false, sender: null });
24
- });
29
+ if (sender && !show) {
30
+ hideModal(domId, sender);
25
31
  }
26
32
  });
27
-
28
- return [modal, setModal];
29
33
  };
30
34
 
31
35
  export {
@@ -32,8 +32,8 @@ class AUTracking {
32
32
  }
33
33
 
34
34
  init() {
35
- let gaNewElem: any = {};
36
- let gaElems: any = {};
35
+ const gaNewElem: any = {};
36
+ const gaElems: any = {};
37
37
  const currdate: any = new Date();
38
38
 
39
39
  ((i, s, o, g, r, a, m): void => {
@@ -42,7 +42,7 @@ class AUTracking {
42
42
  (i[r].q = i[r].q || []).push(arguments);
43
43
  }, i[r].l = 1 * currdate;
44
44
  a = s.createElement(o),
45
- m = s.getElementsByTagName(o)[0];
45
+ m = s.getElementsByTagName(o)[0];
46
46
  a.async = 1;
47
47
  a.src = g;
48
48
  m.parentNode.insertBefore(a, m);
@@ -8,9 +8,6 @@ export default {
8
8
  title: 'Delphinus/Button',
9
9
  component: AUButtonComponent,
10
10
  // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
11
- argTypes: {
12
- backgroundColor: { control: 'color' },
13
- },
14
11
  decorators: [
15
12
  (Story) => { // , context) => {
16
13
  /*
@@ -20,7 +17,7 @@ export default {
20
17
  */
21
18
  return (
22
19
  <div>
23
- <Story />
20
+ {Story()}
24
21
  </div>
25
22
  );
26
23
  }
@@ -0,0 +1,92 @@
1
+ import React from 'react';
2
+ import { ComponentStory, ComponentMeta } from '@storybook/react';
3
+ import { useArgs } from '@storybook/client-api';
4
+
5
+ import AUModalComponent from '../src/components/AUModalComponent';
6
+ import AUButtonComponent from '../src/components/AUButtonComponent';
7
+
8
+ // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
9
+ export default {
10
+ title: 'Delphinus/Modal',
11
+ component: AUModalComponent,
12
+ // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
13
+ decorators: [
14
+ (Story) => {
15
+ // , context) => {
16
+ /*
17
+ const { args: { themeColor, themeMode } } = context;
18
+ const themeColorClass = themeColor === 'none' ? '' : `theme--${themeColor}`;
19
+ const themeModeClass = themeMode === 'light' ? '' : `theme--${themeMode}`;
20
+ */
21
+ return (
22
+ <div className="page">
23
+ {Story()}
24
+ </div>
25
+ );
26
+ },
27
+ ],
28
+ } as ComponentMeta<typeof AUModalComponent>;
29
+
30
+ const fakeBtn = document.createElement('button');
31
+
32
+ // More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
33
+ const Template: ComponentStory<typeof AUModalComponent> = (args) => {
34
+ const [_, updateArgs] = useArgs();
35
+ return (
36
+ <div className="page__content__block">
37
+ <AUButtonComponent
38
+ label="Open modal"
39
+ onClick={() => updateArgs({ ...args, show: true })}
40
+ />
41
+ <AUModalComponent {...args} onClose={() => updateArgs({ ...args, show: false })}>
42
+ <div className="modal-view__body">
43
+ <h2 className="modal-view__header">
44
+ The quick brown fox jumps over the lazy dog
45
+ </h2>
46
+ <div className="modal-view__content">
47
+ <p>"The quick brown fox jumps over the lazy dog" is an English-language pangram—a sentence that contains all of the letters of the alphabet. It is commonly used for touch-typing practice, testing typewriters and computer keyboards, displaying examples of fonts, and other applications involving text where the use of all letters in the alphabet is desired. Owing to its brevity and coherence, it has become widely known.</p><h3>History</h3><p>The earliest known appearance of the phrase is from The Boston Journal. In an article titled "Current Notes" in the February 10, 1885, morning edition, the phrase is mentioned as a good practice sentence for writing students: "A favorite copy set by writing teachers for their pupils is the following, because it contains every letter of the alphabet: 'A quick brown fox jumps over the lazy dog.'"[1] Dozens of other newspapers published the phrase over the next few months, all using the version of the sentence starting with "A" rather than "The".[2] The earliest known use of the phrase in its modern form (starting with "The") is from the 1888 book Illustrative Shorthand by Linda Bronson.[3] The modern form (starting with "The") became more common despite the fact that it is slightly longer than the original (starting with "A").</p>
48
+ </div>
49
+ </div>
50
+ </AUModalComponent>
51
+ </div>
52
+ )
53
+ };
54
+
55
+ export const Default = Template.bind({});
56
+ // More on args: https://storybook.js.org/docs/react/writing-stories/args
57
+ Default.args = {
58
+ show: true,
59
+ sender: fakeBtn,
60
+ };
61
+
62
+ export const Hidden = Template.bind({});
63
+ // More on args: https://storybook.js.org/docs/react/writing-stories/args
64
+ Hidden.args = {
65
+ show: false,
66
+ sender: fakeBtn,
67
+ };
68
+
69
+ export const Wide = Template.bind({});
70
+ // More on args: https://storybook.js.org/docs/react/writing-stories/args
71
+ Wide.args = {
72
+ className: 'modal-view modal-view--wide',
73
+ show: true,
74
+ sender: fakeBtn,
75
+ };
76
+
77
+ export const High = Template.bind({});
78
+ // More on args: https://storybook.js.org/docs/react/writing-stories/args
79
+ High.args = {
80
+ className: 'modal-view modal-view--high',
81
+ show: true,
82
+ sender: fakeBtn,
83
+ };
84
+
85
+ export const Non_Closable = Template.bind({});
86
+ // More on args: https://storybook.js.org/docs/react/writing-stories/args
87
+ Non_Closable.args = {
88
+ show: true,
89
+ sender: fakeBtn,
90
+ closeButtonDisabled: true,
91
+ };
92
+
@@ -0,0 +1,116 @@
1
+ import React from 'react';
2
+ import { ComponentStory, ComponentMeta } from '@storybook/react';
3
+
4
+ import AUNotificationComponent from '../src/components/AUNotificationComponent';
5
+ import AUButtonComponent from '../src/components/AUButtonComponent';
6
+
7
+ // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
8
+ export default {
9
+ title: 'Delphinus/Notification',
10
+ component: AUNotificationComponent,
11
+ // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
12
+ decorators: [
13
+ (Story) => { // , context) => {
14
+ /*
15
+ const { args: { themeColor, themeMode } } = context;
16
+ const themeColorClass = themeColor === 'none' ? '' : `theme--${themeColor}`;
17
+ const themeModeClass = themeMode === 'light' ? '' : `theme--${themeMode}`;
18
+ */
19
+ return (
20
+ <div>
21
+ {Story()}
22
+ </div>
23
+ );
24
+ }
25
+ ]
26
+ } as ComponentMeta<typeof AUNotificationComponent>;
27
+
28
+ // More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
29
+ const Template: ComponentStory<typeof AUNotificationComponent> = (args) => <AUNotificationComponent {...args} />;
30
+
31
+ export const Default = Template.bind({});
32
+ // More on args: https://storybook.js.org/docs/react/writing-stories/args
33
+ Default.args = {
34
+ header: 'This is a notification',
35
+ content: [
36
+ <p>This might be of importance...</p>
37
+ ],
38
+ };
39
+
40
+ export const Warning = Template.bind({});
41
+ Warning.args = {
42
+ header: 'Oops! An error has occurred',
43
+ type: 'warning',
44
+ content: [
45
+ <p>Shit's fucked, yo!</p>
46
+ ],
47
+ };
48
+
49
+ export const Attention = Template.bind({});
50
+ Attention.args = {
51
+ header: 'Please read this',
52
+ type: 'attention',
53
+ content: [
54
+ <p>We really want you to read this.</p>
55
+ ],
56
+ };
57
+
58
+ export const Confirm = Template.bind({});
59
+ Confirm.args = {
60
+ header: 'Great success',
61
+ type: 'confirm',
62
+ content: [
63
+ <p>You the man now, dawg!</p>
64
+ ],
65
+ };
66
+
67
+ export const Single_Action = Template.bind({});
68
+ Single_Action.args = {
69
+ header: 'Do you want to do this?',
70
+ type: 'attention',
71
+ content: [
72
+ <p>Please confirm.</p>
73
+ ],
74
+ actions: [
75
+ <AUButtonComponent
76
+ label="Ok"
77
+ />
78
+ ],
79
+ };
80
+
81
+ export const Multiple_Actions = Template.bind({});
82
+ Multiple_Actions.args = {
83
+ header: 'Do you want to do this?',
84
+ type: 'attention',
85
+ content: [
86
+ <p>Please confirm.</p>
87
+ ],
88
+ actions: [
89
+ <div className="button-container">
90
+ <AUButtonComponent
91
+ label="Ok"
92
+ />
93
+ <AUButtonComponent
94
+ label="Cancel"
95
+ type="text"
96
+ />
97
+ </div>,
98
+ ],
99
+ };
100
+
101
+ export const No_Header = Template.bind({});
102
+ // More on args: https://storybook.js.org/docs/react/writing-stories/args
103
+ No_Header.args = {
104
+ content: [
105
+ <p>This might be of importance...</p>
106
+ ],
107
+ };
108
+
109
+ export const Constrained_Width = Template.bind({});
110
+ // More on args: https://storybook.js.org/docs/react/writing-stories/args
111
+ Constrained_Width.args = {
112
+ content: [
113
+ <p>This might be of importance...</p>
114
+ ],
115
+ classNames: ['notification--constrain-width'],
116
+ };