@lumx/react 2.2.19 → 2.2.20-alpha-type.1
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/esm/_internal/AutocompleteMultiple.js.map +1 -1
- package/esm/_internal/ButtonRoot.js.map +1 -1
- package/esm/_internal/Checkbox2.js +3 -1
- package/esm/_internal/Checkbox2.js.map +1 -1
- package/esm/_internal/ClickAwayProvider.js +90 -12
- package/esm/_internal/ClickAwayProvider.js.map +1 -1
- package/esm/_internal/DatePickerField.js +18 -11
- package/esm/_internal/DatePickerField.js.map +1 -1
- package/esm/_internal/Dialog2.js +2 -2
- package/esm/_internal/Dialog2.js.map +1 -1
- package/esm/_internal/Dropdown2.js.map +1 -1
- package/esm/_internal/GenericBlock.js +90 -0
- package/esm/_internal/GenericBlock.js.map +1 -0
- package/esm/_internal/Lightbox2.js +2 -2
- package/esm/_internal/Lightbox2.js.map +1 -1
- package/esm/_internal/LinkPreview.js +22 -12
- package/esm/_internal/LinkPreview.js.map +1 -1
- package/esm/_internal/Popover2.js +21 -8
- package/esm/_internal/Popover2.js.map +1 -1
- package/esm/_internal/SelectMultiple.js +16 -4
- package/esm/_internal/SelectMultiple.js.map +1 -1
- package/esm/_internal/TextField.js.map +1 -1
- package/esm/_internal/Thumbnail2.js.map +1 -1
- package/esm/_internal/alert-dialog.js +2 -2
- package/esm/_internal/autocomplete.js +2 -1
- package/esm/_internal/autocomplete.js.map +1 -1
- package/esm/_internal/button.js +2 -1
- package/esm/_internal/button.js.map +1 -1
- package/esm/_internal/comment-block.js +2 -1
- package/esm/_internal/comment-block.js.map +1 -1
- package/esm/_internal/date-picker.js +3 -2
- package/esm/_internal/date-picker.js.map +1 -1
- package/esm/_internal/dialog.js +2 -2
- package/esm/_internal/dropdown.js +2 -1
- package/esm/_internal/dropdown.js.map +1 -1
- package/esm/_internal/expansion-panel.js +1 -1
- package/esm/_internal/generic-block.js +12 -0
- package/esm/_internal/generic-block.js.map +1 -0
- package/esm/_internal/lightbox.js +3 -2
- package/esm/_internal/lightbox.js.map +1 -1
- package/esm/_internal/popover.js +2 -1
- package/esm/_internal/popover.js.map +1 -1
- package/esm/_internal/select.js +2 -1
- package/esm/_internal/select.js.map +1 -1
- package/esm/_internal/side-navigation.js +2 -1
- package/esm/_internal/side-navigation.js.map +1 -1
- package/esm/_internal/slideshow.js +2 -1
- package/esm/_internal/slideshow.js.map +1 -1
- package/esm/_internal/text-field.js +2 -1
- package/esm/_internal/text-field.js.map +1 -1
- package/esm/_internal/tooltip.js +2 -1
- package/esm/_internal/tooltip.js.map +1 -1
- package/esm/_internal/type.js.map +1 -1
- package/esm/_internal/useFocusTrap.js +62 -78
- package/esm/_internal/useFocusTrap.js.map +1 -1
- package/esm/index.js +3 -2
- package/esm/index.js.map +1 -1
- package/package.json +5 -5
- package/src/components/autocomplete/Autocomplete.tsx +4 -4
- package/src/components/button/Button.stories.tsx +1 -0
- package/src/components/button/ButtonRoot.tsx +4 -4
- package/src/components/checkbox/Checkbox.tsx +2 -1
- package/src/components/checkbox/__snapshots__/Checkbox.test.tsx.snap +4 -0
- package/src/components/date-picker/DatePickerField.tsx +15 -16
- package/src/components/date-picker/types.ts +2 -2
- package/src/components/dialog/Dialog.stories.tsx +57 -13
- package/src/components/dialog/Dialog.tsx +3 -3
- package/src/components/dialog/__snapshots__/Dialog.test.tsx.snap +82 -14
- package/src/components/dropdown/Dropdown.tsx +4 -3
- package/src/components/generic-block/GenericBlock.stories.tsx +149 -0
- package/src/components/generic-block/GenericBlock.test.tsx +28 -0
- package/src/components/generic-block/GenericBlock.tsx +120 -0
- package/src/components/generic-block/__snapshots__/GenericBlock.test.tsx.snap +92 -0
- package/src/components/generic-block/index.ts +1 -0
- package/src/components/lightbox/Lightbox.tsx +1 -1
- package/src/components/link-preview/LinkPreview.test.tsx +50 -55
- package/src/components/link-preview/LinkPreview.tsx +43 -16
- package/src/components/popover/Popover.tsx +20 -4
- package/src/components/select/Select.stories.tsx +2 -0
- package/src/components/select/Select.tsx +11 -1
- package/src/components/select/SelectMultiple.stories.tsx +2 -0
- package/src/components/select/SelectMultiple.tsx +11 -1
- package/src/components/select/constants.ts +2 -0
- package/src/components/table/__snapshots__/Table.test.tsx.snap +5 -0
- package/src/components/text-field/TextField.tsx +4 -4
- package/src/components/thumbnail/Thumbnail.tsx +2 -2
- package/src/hooks/useCallbackOnEscape.ts +21 -13
- package/src/hooks/useFocusTrap.ts +68 -51
- package/src/index.ts +1 -0
- package/src/stories/generated/GenericBlock/Demos.stories.tsx +6 -0
- package/src/utils/focus/getFirstAndLastFocusable.test.ts +6 -0
- package/src/utils/focus/getFirstAndLastFocusable.ts +2 -2
- package/src/utils/makeListenerTowerContext.ts +32 -0
- package/src/utils/type.ts +3 -0
- package/types.d.ts +70 -16
- package/src/components/link-preview/__snapshots__/LinkPreview.test.tsx.snap +0 -51
|
@@ -8,6 +8,7 @@ exports[`<Checkbox> Props should use the given props 1`] = `
|
|
|
8
8
|
className="lumx-checkbox__input-wrapper"
|
|
9
9
|
>
|
|
10
10
|
<input
|
|
11
|
+
aria-describedby="fixedId-helper"
|
|
11
12
|
className="lumx-checkbox__input-native"
|
|
12
13
|
id="fixedId"
|
|
13
14
|
onChange={[Function]}
|
|
@@ -41,6 +42,7 @@ exports[`<Checkbox> Props should use the given props 1`] = `
|
|
|
41
42
|
</InputLabel>
|
|
42
43
|
<InputHelper
|
|
43
44
|
className="lumx-checkbox__helper"
|
|
45
|
+
id="fixedId-helper"
|
|
44
46
|
kind="info"
|
|
45
47
|
theme="light"
|
|
46
48
|
>
|
|
@@ -58,6 +60,7 @@ exports[`<Checkbox> Props should use the given props while passing custom props
|
|
|
58
60
|
className="lumx-checkbox__input-wrapper"
|
|
59
61
|
>
|
|
60
62
|
<input
|
|
63
|
+
aria-describedby="fixedId-helper"
|
|
61
64
|
aria-labelledby="labelledby-id"
|
|
62
65
|
className="lumx-checkbox__input-native"
|
|
63
66
|
id="fixedId"
|
|
@@ -92,6 +95,7 @@ exports[`<Checkbox> Props should use the given props while passing custom props
|
|
|
92
95
|
</InputLabel>
|
|
93
96
|
<InputHelper
|
|
94
97
|
className="lumx-checkbox__helper"
|
|
98
|
+
id="fixedId-helper"
|
|
95
99
|
kind="info"
|
|
96
100
|
theme="light"
|
|
97
101
|
>
|
|
@@ -62,7 +62,7 @@ export const DatePickerField: Comp<DatePickerFieldProps, HTMLDivElement> = forwa
|
|
|
62
62
|
value,
|
|
63
63
|
...forwardedProps
|
|
64
64
|
} = props;
|
|
65
|
-
const
|
|
65
|
+
const [wrapperElement, setWrapperElement] = useState<HTMLDivElement | null>(null);
|
|
66
66
|
const anchorRef = useRef(null);
|
|
67
67
|
|
|
68
68
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -83,8 +83,8 @@ export const DatePickerField: Comp<DatePickerFieldProps, HTMLDivElement> = forwa
|
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
// Handle focus trap.
|
|
86
|
-
const
|
|
87
|
-
useFocusTrap(
|
|
86
|
+
const [todayOrSelectedDate, setTodayOrSelectedDate] = useState<HTMLButtonElement | null>(null);
|
|
87
|
+
useFocusTrap(isOpen && wrapperElement, todayOrSelectedDate);
|
|
88
88
|
|
|
89
89
|
const onTextFieldChange = (textFieldValue: string, textFieldName?: string, event?: SyntheticEvent) => {
|
|
90
90
|
if (!textFieldValue) {
|
|
@@ -121,19 +121,18 @@ export const DatePickerField: Comp<DatePickerFieldProps, HTMLDivElement> = forwa
|
|
|
121
121
|
closeOnClickAway
|
|
122
122
|
closeOnEscape
|
|
123
123
|
>
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
</div>
|
|
124
|
+
<DatePicker
|
|
125
|
+
ref={setWrapperElement}
|
|
126
|
+
locale={locale}
|
|
127
|
+
maxDate={maxDate}
|
|
128
|
+
minDate={minDate}
|
|
129
|
+
value={value}
|
|
130
|
+
onChange={onDatePickerChange}
|
|
131
|
+
todayOrSelectedDateRef={setTodayOrSelectedDate}
|
|
132
|
+
defaultMonth={defaultMonth}
|
|
133
|
+
nextButtonProps={nextButtonProps}
|
|
134
|
+
previousButtonProps={previousButtonProps}
|
|
135
|
+
/>
|
|
137
136
|
</Popover>
|
|
138
137
|
) : null}
|
|
139
138
|
</>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IconButtonProps } from '@lumx/react';
|
|
2
2
|
import { GenericProps } from '@lumx/react/utils';
|
|
3
|
-
import {
|
|
3
|
+
import { Ref } from 'react';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Defines the props of the component.
|
|
@@ -20,7 +20,7 @@ export interface DatePickerProps extends GenericProps {
|
|
|
20
20
|
previousButtonProps: Pick<IconButtonProps, 'label'> &
|
|
21
21
|
Omit<IconButtonProps, 'label' | 'onClick' | 'icon' | 'emphasis'>;
|
|
22
22
|
/** Reference to the <button> element corresponding to the current date or the selected date. */
|
|
23
|
-
todayOrSelectedDateRef?:
|
|
23
|
+
todayOrSelectedDateRef?: Ref<HTMLButtonElement>;
|
|
24
24
|
/** Currently selected date. */
|
|
25
25
|
value: Date | undefined;
|
|
26
26
|
/** On change callback. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import noop from 'lodash/noop';
|
|
1
2
|
import { mdiClose } from '@lumx/icons';
|
|
2
3
|
import {
|
|
3
4
|
AlertDialog,
|
|
@@ -17,7 +18,8 @@ import {
|
|
|
17
18
|
} from '@lumx/react';
|
|
18
19
|
import { DIALOG_TRANSITION_DURATION } from '@lumx/core/js/constants';
|
|
19
20
|
import { select } from '@storybook/addon-knobs';
|
|
20
|
-
import React, { RefObject, useRef, useState } from 'react';
|
|
21
|
+
import React, { RefObject, useCallback, useRef, useState } from 'react';
|
|
22
|
+
import { useBooleanState } from '@lumx/react/hooks/useBooleanState';
|
|
21
23
|
import { Dialog, DialogSizes } from './Dialog';
|
|
22
24
|
import { loremIpsum } from '../../stories/knobs/lorem';
|
|
23
25
|
import { chromaticForceScreenSize } from '../../stories/chromaticForceScreenSize';
|
|
@@ -43,8 +45,8 @@ const footer = <footer className="lumx-spacing-padding">Dialog footer</footer>;
|
|
|
43
45
|
function useOpenButton(theme: Theme, defaultState = true) {
|
|
44
46
|
const buttonRef = useRef() as RefObject<HTMLButtonElement>;
|
|
45
47
|
const [isOpen, setOpen] = useState(defaultState);
|
|
46
|
-
const openDialog = () => setOpen(true);
|
|
47
|
-
const closeDialog = () => setOpen(false);
|
|
48
|
+
const openDialog = useCallback(() => setOpen(true), []);
|
|
49
|
+
const closeDialog = useCallback(() => setOpen(false), []);
|
|
48
50
|
|
|
49
51
|
return {
|
|
50
52
|
button: (
|
|
@@ -301,6 +303,9 @@ export const DialogFocusTrap = ({ theme }: any) => {
|
|
|
301
303
|
};
|
|
302
304
|
const [date, setDate] = useState<Date | undefined>(new Date('2020-05-18'));
|
|
303
305
|
|
|
306
|
+
const datePickerDialogButtonRef = useRef<HTMLButtonElement>(null);
|
|
307
|
+
const [isDatePickerDialogOpen, closeDatePickerDialog, openDatePickerDialog] = useBooleanState(false);
|
|
308
|
+
|
|
304
309
|
return (
|
|
305
310
|
<>
|
|
306
311
|
{button}
|
|
@@ -314,6 +319,10 @@ export const DialogFocusTrap = ({ theme }: any) => {
|
|
|
314
319
|
/>
|
|
315
320
|
</header>
|
|
316
321
|
<div className="lumx-spacing-padding-horizontal-huge lumx-spacing-padding-bottom-huge">
|
|
322
|
+
{/* Testing hidden input do not count in th focus trap*/}
|
|
323
|
+
<input hidden type="file" />
|
|
324
|
+
<input type="hidden" />
|
|
325
|
+
|
|
317
326
|
<div className="lumx-spacing-margin-bottom-huge">
|
|
318
327
|
The text field should capture the focus on open and a focus trap should be in place.
|
|
319
328
|
</div>
|
|
@@ -335,16 +344,51 @@ export const DialogFocusTrap = ({ theme }: any) => {
|
|
|
335
344
|
/>
|
|
336
345
|
|
|
337
346
|
<FlexBox orientation="horizontal" hAlign="bottom" gap="regular">
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
347
|
+
<Button ref={datePickerDialogButtonRef} onClick={openDatePickerDialog}>
|
|
348
|
+
Open date picker
|
|
349
|
+
</Button>
|
|
350
|
+
<Dialog
|
|
351
|
+
isOpen={isDatePickerDialogOpen}
|
|
352
|
+
parentElement={datePickerDialogButtonRef}
|
|
353
|
+
onClose={closeDatePickerDialog}
|
|
354
|
+
>
|
|
355
|
+
<header>
|
|
356
|
+
<Toolbar
|
|
357
|
+
label={<h1 className="lumx-typography-title">Date picker</h1>}
|
|
358
|
+
after={
|
|
359
|
+
<IconButton
|
|
360
|
+
label="Close"
|
|
361
|
+
icon={mdiClose}
|
|
362
|
+
onClick={closeDatePickerDialog}
|
|
363
|
+
emphasis={Emphasis.low}
|
|
364
|
+
/>
|
|
365
|
+
}
|
|
366
|
+
/>
|
|
367
|
+
</header>
|
|
368
|
+
<div className="lumx-spacing-padding">
|
|
369
|
+
<DatePickerField
|
|
370
|
+
locale="fr"
|
|
371
|
+
label="Start date"
|
|
372
|
+
placeholder="Pick a date"
|
|
373
|
+
theme={theme}
|
|
374
|
+
onChange={setDate}
|
|
375
|
+
value={date}
|
|
376
|
+
nextButtonProps={{ label: 'Next month' }}
|
|
377
|
+
previousButtonProps={{ label: 'Previous month' }}
|
|
378
|
+
/>
|
|
379
|
+
<DatePickerField
|
|
380
|
+
locale="fr"
|
|
381
|
+
label="Start date"
|
|
382
|
+
placeholder="Pick a date"
|
|
383
|
+
theme={theme}
|
|
384
|
+
onChange={noop}
|
|
385
|
+
value={undefined}
|
|
386
|
+
nextButtonProps={{ label: 'Next month' }}
|
|
387
|
+
previousButtonProps={{ label: 'Previous month' }}
|
|
388
|
+
defaultMonth={new Date('2020-05-18')}
|
|
389
|
+
/>
|
|
390
|
+
</div>
|
|
391
|
+
</Dialog>
|
|
348
392
|
|
|
349
393
|
<Select
|
|
350
394
|
className="lumx-spacing-margin-left-huge"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { Children, forwardRef, ReactElement, ReactNode, RefObject, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import React, { Children, forwardRef, ReactElement, ReactNode, Ref, RefObject, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
|
|
4
4
|
import classNames from 'classnames';
|
|
@@ -42,7 +42,7 @@ export interface DialogProps extends GenericProps {
|
|
|
42
42
|
/** Reference to the parent element that triggered modal opening (will get back focus on close). */
|
|
43
43
|
parentElement?: RefObject<HTMLElement>;
|
|
44
44
|
/** Reference to the dialog content element. */
|
|
45
|
-
contentRef?:
|
|
45
|
+
contentRef?: Ref<HTMLDivElement>;
|
|
46
46
|
/** Reference to the of the element that should get the focus when the dialogs opens. By default, the first child will take focus. */
|
|
47
47
|
focusElement?: RefObject<HTMLElement>;
|
|
48
48
|
/** Whether to keep the dialog open on clickaway or escape press. */
|
|
@@ -142,7 +142,7 @@ export const Dialog: Comp<DialogProps, HTMLDivElement> = forwardRef((props, ref)
|
|
|
142
142
|
const localContentRef = useRef<HTMLDivElement>(null);
|
|
143
143
|
// Handle focus trap.
|
|
144
144
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
145
|
-
useFocusTrap(wrapperRef.current, focusElement?.current);
|
|
145
|
+
useFocusTrap(isOpen && wrapperRef.current, focusElement?.current);
|
|
146
146
|
|
|
147
147
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
148
148
|
useDisableBodyScroll(isOpen && localContentRef.current);
|
|
@@ -49,6 +49,13 @@ exports[`<Dialog> Snapshots and structure should render story DialogFocusTrap 1`
|
|
|
49
49
|
<div
|
|
50
50
|
className="lumx-spacing-padding-horizontal-huge lumx-spacing-padding-bottom-huge"
|
|
51
51
|
>
|
|
52
|
+
<input
|
|
53
|
+
hidden={true}
|
|
54
|
+
type="file"
|
|
55
|
+
/>
|
|
56
|
+
<input
|
|
57
|
+
type="hidden"
|
|
58
|
+
/>
|
|
52
59
|
<div
|
|
53
60
|
className="lumx-spacing-margin-bottom-huge"
|
|
54
61
|
>
|
|
@@ -80,23 +87,84 @@ exports[`<Dialog> Snapshots and structure should render story DialogFocusTrap 1`
|
|
|
80
87
|
hAlign="bottom"
|
|
81
88
|
orientation="horizontal"
|
|
82
89
|
>
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
<Button
|
|
91
|
+
emphasis="high"
|
|
92
|
+
onClick={[Function]}
|
|
93
|
+
size="m"
|
|
94
|
+
theme="light"
|
|
95
|
+
>
|
|
96
|
+
Open date picker
|
|
97
|
+
</Button>
|
|
98
|
+
<Dialog
|
|
99
|
+
isOpen={false}
|
|
100
|
+
onClose={[Function]}
|
|
101
|
+
parentElement={
|
|
94
102
|
Object {
|
|
95
|
-
"
|
|
103
|
+
"current": null,
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
size="big"
|
|
107
|
+
>
|
|
108
|
+
<header>
|
|
109
|
+
<Toolbar
|
|
110
|
+
after={
|
|
111
|
+
<IconButton
|
|
112
|
+
emphasis="low"
|
|
113
|
+
icon="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
|
|
114
|
+
label="Close"
|
|
115
|
+
onClick={[Function]}
|
|
116
|
+
size="m"
|
|
117
|
+
theme="light"
|
|
118
|
+
/>
|
|
119
|
+
}
|
|
120
|
+
label={
|
|
121
|
+
<h1
|
|
122
|
+
className="lumx-typography-title"
|
|
123
|
+
>
|
|
124
|
+
Date picker
|
|
125
|
+
</h1>
|
|
126
|
+
}
|
|
127
|
+
/>
|
|
128
|
+
</header>
|
|
129
|
+
<div
|
|
130
|
+
className="lumx-spacing-padding"
|
|
131
|
+
>
|
|
132
|
+
<DatePickerField
|
|
133
|
+
label="Start date"
|
|
134
|
+
locale="fr"
|
|
135
|
+
nextButtonProps={
|
|
136
|
+
Object {
|
|
137
|
+
"label": "Next month",
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
onChange={[Function]}
|
|
141
|
+
placeholder="Pick a date"
|
|
142
|
+
previousButtonProps={
|
|
143
|
+
Object {
|
|
144
|
+
"label": "Previous month",
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
value={2020-05-18T00:00:00.000Z}
|
|
148
|
+
/>
|
|
149
|
+
<DatePickerField
|
|
150
|
+
defaultMonth={2020-05-18T00:00:00.000Z}
|
|
151
|
+
label="Start date"
|
|
152
|
+
locale="fr"
|
|
153
|
+
nextButtonProps={
|
|
154
|
+
Object {
|
|
155
|
+
"label": "Next month",
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
onChange={[Function]}
|
|
159
|
+
placeholder="Pick a date"
|
|
160
|
+
previousButtonProps={
|
|
161
|
+
Object {
|
|
162
|
+
"label": "Previous month",
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
</Dialog>
|
|
100
168
|
<Select
|
|
101
169
|
className="lumx-spacing-margin-left-huge"
|
|
102
170
|
isOpen={false}
|
|
@@ -3,7 +3,7 @@ import React, { cloneElement, forwardRef, useMemo, useRef } from 'react';
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
|
|
5
5
|
import { List, ListProps } from '@lumx/react/components/list/List';
|
|
6
|
-
import { Offset, Placement, Popover } from '@lumx/react/components/popover/Popover';
|
|
6
|
+
import { Offset, Placement, Popover, PopoverProps } from '@lumx/react/components/popover/Popover';
|
|
7
7
|
import { useInfiniteScroll } from '@lumx/react/hooks/useInfiniteScroll';
|
|
8
8
|
import { Comp, GenericProps, getRootClassName, handleBasicClasses, isComponent } from '@lumx/react/utils';
|
|
9
9
|
|
|
@@ -11,10 +11,11 @@ import { Comp, GenericProps, getRootClassName, handleBasicClasses, isComponent }
|
|
|
11
11
|
* Defines the props of the component.
|
|
12
12
|
*/
|
|
13
13
|
export interface DropdownProps extends GenericProps {
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Reference to the element around which the dropdown is placed.
|
|
15
16
|
* @see {@link PopoverProps#anchorRef}
|
|
16
17
|
*/
|
|
17
|
-
anchorRef:
|
|
18
|
+
anchorRef: PopoverProps['anchorRef'];
|
|
18
19
|
/** Dropdown content. */
|
|
19
20
|
children: React.ReactNode;
|
|
20
21
|
/**
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { mdiPencil } from '@lumx/icons';
|
|
3
|
+
import { GenericBlock, Button, Icon, Size, Orientation, Alignment } from '@lumx/react';
|
|
4
|
+
|
|
5
|
+
export default { title: 'LumX components/generic-block/GenericBlock' };
|
|
6
|
+
|
|
7
|
+
export const Horizontal = ({ theme }: any) => (
|
|
8
|
+
<GenericBlock
|
|
9
|
+
orientation={Orientation.horizontal}
|
|
10
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
11
|
+
actionsProps={{
|
|
12
|
+
style: { border: '1px solid red' },
|
|
13
|
+
}}
|
|
14
|
+
figureProps={{
|
|
15
|
+
style: { border: '1px solid red' },
|
|
16
|
+
}}
|
|
17
|
+
contentProps={{
|
|
18
|
+
style: { border: '1px solid red' },
|
|
19
|
+
}}
|
|
20
|
+
actions={<Button theme={theme}>Button</Button>}
|
|
21
|
+
>
|
|
22
|
+
Content
|
|
23
|
+
</GenericBlock>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export const HorizontalWithAlignment = ({ theme }: any) => (
|
|
27
|
+
<GenericBlock
|
|
28
|
+
orientation={Orientation.horizontal}
|
|
29
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
30
|
+
actionsProps={{
|
|
31
|
+
fillSpace: true,
|
|
32
|
+
style: { border: '1px solid red' },
|
|
33
|
+
vAlign: 'center',
|
|
34
|
+
}}
|
|
35
|
+
figureProps={{
|
|
36
|
+
style: { border: '1px solid red' },
|
|
37
|
+
}}
|
|
38
|
+
contentProps={{
|
|
39
|
+
style: { border: '1px solid red' },
|
|
40
|
+
}}
|
|
41
|
+
actions={<Button theme={theme}>Centered button</Button>}
|
|
42
|
+
>
|
|
43
|
+
Content
|
|
44
|
+
</GenericBlock>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
export const HorizontalTop = ({ theme }: any) => (
|
|
48
|
+
<GenericBlock
|
|
49
|
+
orientation={Orientation.horizontal}
|
|
50
|
+
hAlign={Alignment.top}
|
|
51
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
52
|
+
actionsProps={{
|
|
53
|
+
style: { border: '1px solid red' },
|
|
54
|
+
}}
|
|
55
|
+
figureProps={{
|
|
56
|
+
style: { border: '1px solid red' },
|
|
57
|
+
}}
|
|
58
|
+
contentProps={{
|
|
59
|
+
style: { border: '1px solid red' },
|
|
60
|
+
}}
|
|
61
|
+
actions={<Button theme={theme}>Centered button</Button>}
|
|
62
|
+
>
|
|
63
|
+
Content
|
|
64
|
+
</GenericBlock>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
export const Vertical = ({ theme }: any) => (
|
|
68
|
+
<GenericBlock
|
|
69
|
+
orientation={Orientation.vertical}
|
|
70
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
71
|
+
actionsProps={{
|
|
72
|
+
fillSpace: true,
|
|
73
|
+
style: { border: '1px solid red' },
|
|
74
|
+
}}
|
|
75
|
+
figureProps={{
|
|
76
|
+
style: { border: '1px solid red' },
|
|
77
|
+
}}
|
|
78
|
+
contentProps={{
|
|
79
|
+
style: { border: '1px solid red' },
|
|
80
|
+
}}
|
|
81
|
+
actions={<Button theme={theme}>Button</Button>}
|
|
82
|
+
>
|
|
83
|
+
Content
|
|
84
|
+
</GenericBlock>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
export const GapSizes = ({ theme }: any) => (
|
|
88
|
+
<>
|
|
89
|
+
<GenericBlock
|
|
90
|
+
orientation={Orientation.vertical}
|
|
91
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
92
|
+
gap={Size.regular}
|
|
93
|
+
style={{ marginBottom: 40 }}
|
|
94
|
+
actionsProps={{
|
|
95
|
+
style: { border: '1px solid red' },
|
|
96
|
+
}}
|
|
97
|
+
figureProps={{
|
|
98
|
+
style: { border: '1px solid red' },
|
|
99
|
+
}}
|
|
100
|
+
contentProps={{
|
|
101
|
+
style: { border: '1px solid red' },
|
|
102
|
+
}}
|
|
103
|
+
actions={<Button theme={theme}>Button</Button>}
|
|
104
|
+
>
|
|
105
|
+
<h2>Small gap size</h2>
|
|
106
|
+
<p>For small blocks</p>
|
|
107
|
+
</GenericBlock>
|
|
108
|
+
|
|
109
|
+
<GenericBlock
|
|
110
|
+
orientation={Orientation.vertical}
|
|
111
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
112
|
+
gap={Size.big}
|
|
113
|
+
style={{ marginBottom: 40 }}
|
|
114
|
+
actionsProps={{
|
|
115
|
+
style: { border: '1px solid red' },
|
|
116
|
+
}}
|
|
117
|
+
figureProps={{
|
|
118
|
+
style: { border: '1px solid red' },
|
|
119
|
+
}}
|
|
120
|
+
contentProps={{
|
|
121
|
+
style: { border: '1px solid red' },
|
|
122
|
+
}}
|
|
123
|
+
actions={<Button theme={theme}>Button</Button>}
|
|
124
|
+
>
|
|
125
|
+
<h2>Medium gap size</h2>
|
|
126
|
+
<p>For medium blocks</p>
|
|
127
|
+
</GenericBlock>
|
|
128
|
+
|
|
129
|
+
<GenericBlock
|
|
130
|
+
orientation={Orientation.vertical}
|
|
131
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
132
|
+
gap={Size.huge}
|
|
133
|
+
style={{ marginBottom: 40 }}
|
|
134
|
+
actionsProps={{
|
|
135
|
+
style: { border: '1px solid red' },
|
|
136
|
+
}}
|
|
137
|
+
figureProps={{
|
|
138
|
+
style: { border: '1px solid red' },
|
|
139
|
+
}}
|
|
140
|
+
contentProps={{
|
|
141
|
+
style: { border: '1px solid red' },
|
|
142
|
+
}}
|
|
143
|
+
actions={<Button theme={theme}>Button</Button>}
|
|
144
|
+
>
|
|
145
|
+
<h2>Big gap size</h2>
|
|
146
|
+
<p>For large blocks</p>
|
|
147
|
+
</GenericBlock>
|
|
148
|
+
</>
|
|
149
|
+
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { mount, shallow } from 'enzyme';
|
|
3
|
+
import 'jest-enzyme';
|
|
4
|
+
import { commonTestsSuite, itShouldRenderStories } from '@lumx/react/testing/utils';
|
|
5
|
+
|
|
6
|
+
import { GenericBlock, GenericBlockProps } from './GenericBlock';
|
|
7
|
+
import * as stories from '../../stories/generated/GenericBlock/Demos.stories';
|
|
8
|
+
|
|
9
|
+
const CLASSNAME = GenericBlock.className as string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Mounts the component and returns common DOM elements / data needed in multiple tests further down.
|
|
13
|
+
*/
|
|
14
|
+
const setup = (props: Partial<GenericBlockProps> = {}, shallowRendering = true) => {
|
|
15
|
+
const renderer: any = shallowRendering ? shallow : mount;
|
|
16
|
+
const wrapper: any = renderer(<GenericBlock {...(props as any)} />);
|
|
17
|
+
return { props, wrapper };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe(`<${GenericBlock.displayName}>`, () => {
|
|
21
|
+
// 1. Test render via snapshot.
|
|
22
|
+
describe('Snapshots and structure', () => {
|
|
23
|
+
itShouldRenderStories(stories, GenericBlock);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Common tests suite.
|
|
27
|
+
commonTestsSuite(setup, { className: 'wrapper', prop: 'wrapper' }, { className: CLASSNAME });
|
|
28
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React, { forwardRef, ReactNode } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { Comp, getRootClassName } from '@lumx/react/utils';
|
|
4
|
+
import { Orientation, Size, FlexBox, FlexBoxProps, Alignment, HorizontalAlignment } from '@lumx/react';
|
|
5
|
+
|
|
6
|
+
export interface GenericBlockProps extends FlexBoxProps {
|
|
7
|
+
/** Component to use as visual element. */
|
|
8
|
+
figure?: ReactNode;
|
|
9
|
+
/** Actions to set after the main content. */
|
|
10
|
+
actions?: ReactNode;
|
|
11
|
+
/** Main content to display */
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
/** Orientation of the 3 sections */
|
|
14
|
+
orientation?: FlexBoxProps['orientation'];
|
|
15
|
+
/** Horizontal alignment. */
|
|
16
|
+
hAlign?: FlexBoxProps['hAlign'];
|
|
17
|
+
/** Vertical alignment. */
|
|
18
|
+
vAlign?: FlexBoxProps['vAlign'];
|
|
19
|
+
/**
|
|
20
|
+
* The props to forward to the content.
|
|
21
|
+
* By default, the content will have the same alignment as wrapper.
|
|
22
|
+
*/
|
|
23
|
+
contentProps?: Omit<FlexBoxProps, 'children'>;
|
|
24
|
+
/** props to forward to the actions element. */
|
|
25
|
+
actionsProps?: Omit<FlexBoxProps, 'children'>;
|
|
26
|
+
/** props to forward to the figure element. */
|
|
27
|
+
figureProps?: Omit<FlexBoxProps, 'children'>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Component display name.
|
|
32
|
+
*/
|
|
33
|
+
const COMPONENT_NAME = 'GenericBlock';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Component default class name and class prefix.
|
|
37
|
+
*/
|
|
38
|
+
const CLASSNAME = getRootClassName(COMPONENT_NAME);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Component default props.
|
|
42
|
+
*/
|
|
43
|
+
const DEFAULT_PROPS: Partial<GenericBlockProps> = {
|
|
44
|
+
gap: Size.regular,
|
|
45
|
+
orientation: Orientation.vertical,
|
|
46
|
+
hAlign: Alignment.center,
|
|
47
|
+
vAlign: Alignment.center,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The GenericBlock is a layout component made of 3 sections that can be
|
|
52
|
+
* displayed either horizontally of vertically with the same gap between each section.
|
|
53
|
+
*
|
|
54
|
+
* The sections are:
|
|
55
|
+
* * (Optional) `Figure` => A visual element to display before the main content.
|
|
56
|
+
* * (Required) `Content` => The main content displayed
|
|
57
|
+
* * (Optional) `Actions` => One or more actions to set after the element.
|
|
58
|
+
*
|
|
59
|
+
* @see https://www.figma.com/file/lzzrQmsfaXRaOyRfoEogPZ/DS%3A-playground?node-id=1%3A4076
|
|
60
|
+
*/
|
|
61
|
+
export const GenericBlock: Comp<GenericBlockProps, HTMLDivElement> = forwardRef((props, ref) => {
|
|
62
|
+
const {
|
|
63
|
+
className,
|
|
64
|
+
figure,
|
|
65
|
+
figureProps,
|
|
66
|
+
children,
|
|
67
|
+
actions,
|
|
68
|
+
actionsProps,
|
|
69
|
+
gap,
|
|
70
|
+
orientation,
|
|
71
|
+
contentProps,
|
|
72
|
+
...forwardedProps
|
|
73
|
+
} = props;
|
|
74
|
+
|
|
75
|
+
let actionsVAlign: HorizontalAlignment = Alignment.center;
|
|
76
|
+
if (orientation === Orientation.horizontal) {
|
|
77
|
+
actionsVAlign = Alignment.right;
|
|
78
|
+
}
|
|
79
|
+
let contentVAlign: HorizontalAlignment = Alignment.center;
|
|
80
|
+
if (orientation === Orientation.horizontal) {
|
|
81
|
+
contentVAlign = Alignment.left;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<FlexBox
|
|
86
|
+
ref={ref}
|
|
87
|
+
className={classNames(className, CLASSNAME)}
|
|
88
|
+
gap={gap}
|
|
89
|
+
orientation={orientation}
|
|
90
|
+
{...forwardedProps}
|
|
91
|
+
>
|
|
92
|
+
<FlexBox {...figureProps} className={classNames(figureProps?.className, `${CLASSNAME}__figure`)}>
|
|
93
|
+
{figure}
|
|
94
|
+
</FlexBox>
|
|
95
|
+
|
|
96
|
+
{children && (
|
|
97
|
+
<FlexBox
|
|
98
|
+
orientation={Orientation.vertical}
|
|
99
|
+
fillSpace
|
|
100
|
+
vAlign={contentVAlign}
|
|
101
|
+
{...contentProps}
|
|
102
|
+
className={classNames(contentProps?.className, `${CLASSNAME}__content`)}
|
|
103
|
+
>
|
|
104
|
+
{children}
|
|
105
|
+
</FlexBox>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
<FlexBox
|
|
109
|
+
vAlign={actionsVAlign}
|
|
110
|
+
{...actionsProps}
|
|
111
|
+
className={classNames(actionsProps?.className, `${CLASSNAME}__actions`)}
|
|
112
|
+
>
|
|
113
|
+
{actions}
|
|
114
|
+
</FlexBox>
|
|
115
|
+
</FlexBox>
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
GenericBlock.displayName = COMPONENT_NAME;
|
|
119
|
+
GenericBlock.className = CLASSNAME;
|
|
120
|
+
GenericBlock.defaultProps = DEFAULT_PROPS;
|