@mezzanine-ui/react 1.0.0-beta.1 → 1.0.0-beta.3
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/Anchor/Anchor.d.ts +51 -18
- package/Anchor/Anchor.js +15 -15
- package/Anchor/AnchorGroup.d.ts +34 -0
- package/Anchor/AnchorGroup.js +37 -0
- package/Anchor/AnchorItem.d.ts +30 -0
- package/Anchor/AnchorItem.js +65 -0
- package/Anchor/index.d.ts +2 -0
- package/Anchor/index.js +1 -0
- package/Anchor/utils.d.ts +13 -0
- package/Anchor/utils.js +95 -0
- package/AutoComplete/AutoComplete.d.ts +194 -0
- package/AutoComplete/AutoComplete.js +419 -0
- package/AutoComplete/index.d.ts +2 -0
- package/AutoComplete/index.js +1 -0
- package/AutoComplete/useAutoCompleteCreation.d.ts +33 -0
- package/AutoComplete/useAutoCompleteCreation.js +201 -0
- package/AutoComplete/useAutoCompleteKeyboard.d.ts +31 -0
- package/AutoComplete/useAutoCompleteKeyboard.js +149 -0
- package/AutoComplete/useAutoCompleteSearch.d.ts +16 -0
- package/AutoComplete/useAutoCompleteSearch.js +69 -0
- package/AutoComplete/useCreationTracker.d.ts +17 -0
- package/AutoComplete/useCreationTracker.js +47 -0
- package/Badge/Badge.js +2 -2
- package/Breadcrumb/BreadcrumbItem.d.ts +1 -1
- package/Button/Button.js +13 -11
- package/Button/index.d.ts +1 -1
- package/Button/typings.d.ts +27 -4
- package/Description/Description.d.ts +30 -0
- package/Description/Description.js +13 -0
- package/Description/DescriptionContent.d.ts +41 -0
- package/Description/DescriptionContent.js +14 -0
- package/Description/DescriptionGroup.d.ts +13 -0
- package/Description/DescriptionGroup.js +12 -0
- package/Description/DescriptionTitle.d.ts +45 -0
- package/Description/DescriptionTitle.js +17 -0
- package/Description/index.d.ts +8 -0
- package/Description/index.js +4 -0
- package/Dropdown/Dropdown.d.ts +43 -3
- package/Dropdown/Dropdown.js +154 -35
- package/Dropdown/DropdownAction.d.ts +1 -1
- package/Dropdown/DropdownAction.js +1 -4
- package/Dropdown/DropdownItem.d.ts +21 -4
- package/Dropdown/DropdownItem.js +23 -10
- package/Dropdown/DropdownItemCard.d.ts +5 -5
- package/Dropdown/DropdownItemCard.js +11 -10
- package/Dropdown/DropdownStatus.d.ts +2 -2
- package/Dropdown/DropdownStatus.js +29 -0
- package/Dropdown/dropdownKeydownHandler.d.ts +2 -1
- package/Dropdown/dropdownKeydownHandler.js +73 -0
- package/Dropdown/highlightText.js +5 -1
- package/Dropdown/shortcutTextHandler.d.ts +24 -0
- package/Dropdown/shortcutTextHandler.js +171 -0
- package/Form/FormControlContext.d.ts +2 -2
- package/Form/FormField.d.ts +56 -4
- package/Form/FormField.js +10 -6
- package/Form/FormHintText.d.ts +24 -1
- package/Form/FormHintText.js +4 -4
- package/Form/FormLabel.d.ts +6 -3
- package/Form/FormLabel.js +5 -3
- package/Input/Input.d.ts +29 -3
- package/Input/Input.js +22 -6
- package/Input/PasswordStrengthIndicator/PasswordStrengthIndicator.js +1 -1
- package/Modal/Modal.d.ts +103 -11
- package/Modal/Modal.js +14 -9
- package/Modal/ModalBodyForVerification.d.ts +59 -0
- package/Modal/ModalBodyForVerification.js +99 -0
- package/Modal/ModalControl.d.ts +2 -2
- package/Modal/ModalControl.js +1 -1
- package/Modal/ModalFooter.d.ts +119 -1
- package/Modal/ModalFooter.js +15 -3
- package/Modal/ModalHeader.d.ts +26 -7
- package/Modal/ModalHeader.js +33 -7
- package/Modal/index.d.ts +4 -5
- package/Modal/index.js +1 -2
- package/Modal/useModalContainer.d.ts +12 -3
- package/Modal/useModalContainer.js +28 -6
- package/Navigation/CollapsedMenu.d.ts +6 -0
- package/Navigation/CollapsedMenu.js +16 -0
- package/Navigation/Navigation.d.ts +17 -3
- package/Navigation/Navigation.js +48 -33
- package/Navigation/NavigationFooter.js +4 -2
- package/Navigation/NavigationHeader.d.ts +11 -1
- package/Navigation/NavigationHeader.js +6 -3
- package/Navigation/NavigationOption.d.ts +3 -2
- package/Navigation/NavigationOption.js +45 -26
- package/Navigation/NavigationOptionCategory.js +20 -2
- package/Navigation/context.d.ts +2 -0
- package/Navigation/useVisibleItems.d.ts +5 -0
- package/Navigation/useVisibleItems.js +54 -0
- package/NotificationCenter/NotificationCenter.d.ts +124 -0
- package/NotificationCenter/NotificationCenter.js +259 -0
- package/NotificationCenter/NotificationCenterDrawer.d.ts +89 -0
- package/NotificationCenter/index.d.ts +3 -0
- package/NotificationCenter/index.js +1 -0
- package/PageFooter/PageFooter.d.ts +19 -9
- package/PageFooter/PageFooter.js +10 -10
- package/PageHeader/PageHeader.js +4 -12
- package/PageToolbar/PageToolbar.d.ts +2 -6
- package/PageToolbar/utils.js +4 -12
- package/Select/index.d.ts +0 -2
- package/Select/index.js +0 -1
- package/Slider/useSlider.js +1 -1
- package/Table/Table.d.ts +53 -15
- package/Table/Table.js +178 -82
- package/Table/TableContext.d.ts +18 -42
- package/Table/components/TableActionsCell.d.ts +26 -0
- package/Table/components/TableActionsCell.js +78 -0
- package/Table/components/TableBody.d.ts +2 -5
- package/Table/components/TableBody.js +16 -19
- package/Table/components/TableBulkActions.d.ts +15 -0
- package/Table/components/TableBulkActions.js +26 -0
- package/Table/components/TableCell.d.ts +2 -0
- package/Table/components/TableCell.js +42 -10
- package/Table/components/TableColGroup.js +10 -112
- package/Table/components/TableColumnTitleMenu.d.ts +6 -0
- package/Table/components/TableColumnTitleMenu.js +20 -0
- package/Table/components/TableDragHandleCell.d.ts +2 -0
- package/Table/components/TableDragHandleCell.js +8 -1
- package/Table/components/TableExpandCell.d.ts +2 -0
- package/Table/components/TableExpandCell.js +8 -1
- package/Table/components/TableExpandedRow.js +3 -2
- package/Table/components/TableHeader.d.ts +2 -4
- package/Table/components/TableHeader.js +11 -14
- package/Table/components/TableResizeHandle.js +3 -7
- package/Table/components/TableRow.js +54 -20
- package/Table/components/TableSelectionCell.d.ts +5 -0
- package/Table/components/TableSelectionCell.js +12 -1
- package/Table/components/index.d.ts +1 -0
- package/Table/components/index.js +1 -0
- package/Table/hooks/index.d.ts +1 -1
- package/Table/hooks/index.js +1 -1
- package/Table/hooks/useTableDataSource.d.ts +2 -2
- package/Table/hooks/useTableExpansion.js +0 -6
- package/Table/hooks/useTableFixedOffsets.d.ts +1 -1
- package/Table/hooks/useTableFixedOffsets.js +24 -26
- package/Table/hooks/useTableResizedColumns.d.ts +2 -0
- package/Table/hooks/useTableResizedColumns.js +22 -0
- package/Table/hooks/useTableScroll.d.ts +3 -1
- package/Table/hooks/useTableScroll.js +25 -19
- package/Table/hooks/useTableSelection.js +32 -8
- package/Table/hooks/useTableVirtualization.d.ts +1 -1
- package/Table/index.d.ts +4 -4
- package/Table/index.js +5 -3
- package/Table/utils/calculateColumnWidths.d.ts +28 -0
- package/Table/utils/calculateColumnWidths.js +80 -0
- package/Table/utils/index.d.ts +2 -0
- package/Table/utils/index.js +1 -0
- package/Table/utils/useTableRowSelection.d.ts +5 -5
- package/Table/utils/useTableRowSelection.js +14 -6
- package/Tag/TagGroup.d.ts +3 -0
- package/Tag/index.d.ts +2 -0
- package/Tag/index.js +1 -0
- package/Upload/UploadPictureCard.js +1 -1
- package/index.d.ts +36 -20
- package/index.js +26 -7
- package/package.json +4 -4
- package/utils/format-number-with-commas.d.ts +4 -0
- package/utils/format-number-with-commas.js +27 -0
- package/utils/parse-number-with-commas.d.ts +4 -0
- package/utils/parse-number-with-commas.js +22 -0
- package/Modal/ModalActions.d.ts +0 -9
- package/Modal/ModalActions.js +0 -20
- package/Modal/ModalBody.d.ts +0 -7
- package/Modal/ModalBody.js +0 -14
- package/Notification/Notification.d.ts +0 -54
- package/Notification/Notification.js +0 -76
- package/Notification/index.d.ts +0 -3
- package/Notification/index.js +0 -1
- package/Select/AutoComplete.d.ts +0 -107
- package/Select/AutoComplete.js +0 -114
- package/Table/hooks/useTableColumns.d.ts +0 -8
- package/Table/hooks/useTableColumns.js +0 -91
package/Modal/ModalHeader.d.ts
CHANGED
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
2
2
|
export interface ModalHeaderProps extends NativeElementPropsWithoutKeyAndRef<'div'> {
|
|
3
3
|
/**
|
|
4
|
-
* Whether to show
|
|
4
|
+
* Whether to show status type icon.
|
|
5
5
|
* @default false
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
showStatusTypeIcon?: boolean;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Layout of the status type icon relative to title.
|
|
10
|
+
* - 'vertical': Icon above title
|
|
11
|
+
* - 'horizontal': Icon to the left of title
|
|
12
|
+
* @default 'vertical'
|
|
13
|
+
*/
|
|
14
|
+
statusTypeIconLayout?: 'vertical' | 'horizontal';
|
|
15
|
+
/**
|
|
16
|
+
* Supporting text displayed below the title.
|
|
17
|
+
*/
|
|
18
|
+
supportingText?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Alignment of the supporting text.
|
|
21
|
+
* @default 'left'
|
|
22
|
+
*/
|
|
23
|
+
supportingTextAlign?: 'left' | 'center';
|
|
24
|
+
/**
|
|
25
|
+
* The title text of the modal header.
|
|
26
|
+
*/
|
|
27
|
+
title: string;
|
|
28
|
+
/**
|
|
29
|
+
* Alignment of the title.
|
|
30
|
+
* @default 'left'
|
|
12
31
|
*/
|
|
13
|
-
|
|
32
|
+
titleAlign?: 'left' | 'center';
|
|
14
33
|
}
|
|
15
34
|
/**
|
|
16
|
-
* The
|
|
35
|
+
* The React component for `mezzanine` modal header.
|
|
17
36
|
*/
|
|
18
37
|
declare const ModalHeader: import("react").ForwardRefExoticComponent<ModalHeaderProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
19
38
|
export default ModalHeader;
|
package/Modal/ModalHeader.js
CHANGED
|
@@ -1,20 +1,46 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
3
|
import { forwardRef, useContext } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { modalStatusTypeIcons, modalClasses } from '@mezzanine-ui/core/modal';
|
|
5
5
|
import { ModalControlContext } from './ModalControl.js';
|
|
6
|
+
import Typography from '../Typography/Typography.js';
|
|
6
7
|
import Icon from '../Icon/Icon.js';
|
|
7
8
|
import cx from 'clsx';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
* The
|
|
11
|
+
* The React component for `mezzanine` modal header.
|
|
11
12
|
*/
|
|
12
13
|
const ModalHeader = forwardRef(function ModalHeader(props, ref) {
|
|
13
|
-
const {
|
|
14
|
-
const {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const { className, showStatusTypeIcon = false, statusTypeIconLayout = 'vertical', supportingText, supportingTextAlign = 'left', title, titleAlign = 'left', ...rest } = props;
|
|
15
|
+
const { modalStatusType } = useContext(ModalControlContext);
|
|
16
|
+
const iconColor = (type) => {
|
|
17
|
+
switch (type) {
|
|
18
|
+
case 'success':
|
|
19
|
+
return 'success-strong';
|
|
20
|
+
case 'warning':
|
|
21
|
+
return 'warning';
|
|
22
|
+
case 'error':
|
|
23
|
+
return 'error-solid';
|
|
24
|
+
case 'info':
|
|
25
|
+
return 'info-strong';
|
|
26
|
+
case 'email':
|
|
27
|
+
return 'info-strong';
|
|
28
|
+
case 'delete':
|
|
29
|
+
return 'error-solid';
|
|
30
|
+
default:
|
|
31
|
+
return 'neutral';
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
return (jsxs("div", { ...rest, ref: ref, className: cx(modalClasses.modalHeader, {
|
|
35
|
+
[modalClasses.modalHeader + '--horizontal']: statusTypeIconLayout === 'horizontal',
|
|
36
|
+
[modalClasses.modalHeader + '--vertical']: statusTypeIconLayout === 'vertical',
|
|
37
|
+
[modalClasses.modalHeader + '--title-align-left']: titleAlign === 'left',
|
|
38
|
+
[modalClasses.modalHeader + '--title-align-center']: titleAlign === 'center',
|
|
39
|
+
[modalClasses.modalHeader + '--show-modal-status-type-icon']: showStatusTypeIcon,
|
|
40
|
+
}, className), children: [showStatusTypeIcon && (jsx("div", { className: cx(modalClasses.modalHeaderStatusTypeIcon), children: jsx(Icon, { icon: modalStatusTypeIcons[modalStatusType], color: iconColor(modalStatusType), size: 20 }) })), jsxs("div", { className: cx(modalClasses.modalHeaderTitleAndSupportingTextContainer), children: [jsx(Typography, { variant: "h3", color: "text-neutral-solid", className: cx(modalClasses.modalHeaderTitle), title: typeof title === 'string' ? title : undefined, children: title }), jsx(Typography, { variant: "body", color: "text-neutral-strong", className: cx(modalClasses.modalHeaderSupportingText, {
|
|
41
|
+
[modalClasses.modalHeaderSupportingText + '--align-left']: supportingTextAlign === 'left',
|
|
42
|
+
[modalClasses.modalHeaderSupportingText + '--align-center']: supportingTextAlign === 'center',
|
|
43
|
+
}), children: supportingText })] })] }));
|
|
18
44
|
});
|
|
19
45
|
|
|
20
46
|
export { ModalHeader as default };
|
package/Modal/index.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
export type {
|
|
3
|
-
export { default as
|
|
4
|
-
export type { ModalBodyProps } from './ModalBody';
|
|
5
|
-
export { default as ModalBody } from './ModalBody';
|
|
1
|
+
export type { ModalStatusType, ModalSize } from '@mezzanine-ui/core/modal';
|
|
2
|
+
export type { ModalBodyForVerificationProps } from './ModalBodyForVerification';
|
|
3
|
+
export { default as ModalBodyForVerification } from './ModalBodyForVerification';
|
|
6
4
|
export type { ModalFooterProps } from './ModalFooter';
|
|
7
5
|
export { default as ModalFooter } from './ModalFooter';
|
|
8
6
|
export type { ModalHeaderProps } from './ModalHeader';
|
|
9
7
|
export { default as ModalHeader } from './ModalHeader';
|
|
10
8
|
export { default as useModalContainer } from './useModalContainer';
|
|
9
|
+
export type { ModalContainerProps } from './useModalContainer';
|
|
11
10
|
export type { ModalProps } from './Modal';
|
|
12
11
|
export { default } from './Modal';
|
package/Modal/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
export { default as
|
|
2
|
-
export { default as ModalBody } from './ModalBody.js';
|
|
1
|
+
export { default as ModalBodyForVerification } from './ModalBodyForVerification.js';
|
|
3
2
|
export { default as ModalFooter } from './ModalFooter.js';
|
|
4
3
|
export { default as ModalHeader } from './ModalHeader.js';
|
|
5
4
|
export { default as useModalContainer } from './useModalContainer.js';
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { BackdropProps } from '../Backdrop';
|
|
3
|
+
export interface ModalContainerProps extends Pick<BackdropProps, 'className' | 'container' | 'disableCloseOnBackdropClick' | 'disablePortal' | 'onBackdropClick' | 'onClose' | 'open'> {
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
/**
|
|
6
|
+
* Controls whether to disable closing modal while escape key down.
|
|
7
|
+
* @default false
|
|
8
|
+
*/
|
|
9
|
+
disableCloseOnEscapeKeyDown?: boolean;
|
|
10
|
+
}
|
|
2
11
|
export default function useModalContainer(): {
|
|
3
|
-
Container: import("react").ForwardRefExoticComponent<
|
|
4
|
-
defaultOptions: Pick<
|
|
12
|
+
Container: import("react").ForwardRefExoticComponent<ModalContainerProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
13
|
+
defaultOptions: Pick<ModalContainerProps, "className" | "open" | "disablePortal" | "disableCloseOnBackdropClick" | "disableCloseOnEscapeKeyDown">;
|
|
5
14
|
};
|
|
@@ -1,19 +1,41 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { modalClasses } from '@mezzanine-ui/core/modal';
|
|
3
|
-
import { forwardRef } from 'react';
|
|
4
|
-
import
|
|
3
|
+
import { forwardRef, useState } from 'react';
|
|
4
|
+
import { useDocumentEscapeKeyDown } from '../hooks/useDocumentEscapeKeyDown.js';
|
|
5
|
+
import useTopStack from '../_internal/SlideFadeOverlay/useTopStack.js';
|
|
6
|
+
import Backdrop from '../Backdrop/Backdrop.js';
|
|
7
|
+
import Fade from '../Transition/Fade.js';
|
|
8
|
+
import cx from 'clsx';
|
|
5
9
|
|
|
6
10
|
const defaultOptions = {
|
|
7
11
|
className: modalClasses.overlay,
|
|
8
|
-
direction: 'down',
|
|
9
12
|
disableCloseOnBackdropClick: false,
|
|
10
13
|
disableCloseOnEscapeKeyDown: false,
|
|
11
14
|
disablePortal: false,
|
|
12
15
|
open: false,
|
|
13
16
|
};
|
|
14
|
-
const ModalContainer = forwardRef((props, ref)
|
|
15
|
-
const { className = defaultOptions.className,
|
|
16
|
-
|
|
17
|
+
const ModalContainer = forwardRef(function ModalContainer(props, ref) {
|
|
18
|
+
const { children, className = defaultOptions.className, container, disableCloseOnBackdropClick = defaultOptions.disableCloseOnBackdropClick, disableCloseOnEscapeKeyDown = defaultOptions.disableCloseOnEscapeKeyDown, disablePortal = defaultOptions.disablePortal, onBackdropClick, onClose, open = defaultOptions.open, } = props;
|
|
19
|
+
const [exited, setExited] = useState(true);
|
|
20
|
+
/**
|
|
21
|
+
* Escape keydown close: escape will only close the top modal
|
|
22
|
+
*/
|
|
23
|
+
const checkIsOnTheTop = useTopStack(open);
|
|
24
|
+
useDocumentEscapeKeyDown(() => {
|
|
25
|
+
if (!open || disableCloseOnEscapeKeyDown || !onClose) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
return (event) => {
|
|
29
|
+
if (checkIsOnTheTop()) {
|
|
30
|
+
event.stopPropagation();
|
|
31
|
+
onClose();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}, [disableCloseOnEscapeKeyDown, checkIsOnTheTop, open, onClose]);
|
|
35
|
+
if (!open && exited) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return (jsx(Backdrop, { className: cx(className), container: container, disableCloseOnBackdropClick: disableCloseOnBackdropClick, disablePortal: disablePortal, onBackdropClick: onBackdropClick, onClose: onClose, open: open, role: "presentation", children: jsx(Fade, { in: open, onEntered: () => setExited(false), onExited: () => setExited(true), children: jsx("div", { ref: ref, children: children }) }) }));
|
|
17
39
|
});
|
|
18
40
|
function useModalContainer() {
|
|
19
41
|
return {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useRef } from 'react';
|
|
3
|
+
import { navigationClasses } from '@mezzanine-ui/core/navigation';
|
|
4
|
+
import NavigationIconButton from './NavigationIconButton.js';
|
|
5
|
+
import { DotHorizontalIcon } from '@mezzanine-ui/icons';
|
|
6
|
+
import Popper from '../Popper/Popper.js';
|
|
7
|
+
|
|
8
|
+
const CollapsedMenu = ({ items }) => {
|
|
9
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
10
|
+
const targetRef = useRef(null);
|
|
11
|
+
return (jsxs(Fragment, { children: [jsx(NavigationIconButton, { ref: targetRef, icon: DotHorizontalIcon, onClick: () => setMenuOpen(!menuOpen) }), jsx(Popper, { anchor: targetRef.current, open: menuOpen, options: {
|
|
12
|
+
placement: 'right-end',
|
|
13
|
+
}, children: jsx("div", { className: navigationClasses.collapsedMenu, children: jsx("ul", { children: items }) }) })] }));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export { CollapsedMenu };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
2
|
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
3
3
|
import { NavigationOptionProps } from './NavigationOption';
|
|
4
4
|
import { NavigationHeaderProps } from './NavigationHeader';
|
|
5
5
|
import { NavigationFooterProps } from './NavigationFooter';
|
|
6
|
-
|
|
7
|
-
export type
|
|
6
|
+
import { NavigationOptionCategoryProps } from './NavigationOptionCategory';
|
|
7
|
+
export type NavigationChild = ReactElement<NavigationFooterProps> | ReactElement<NavigationHeaderProps> | ReactElement<NavigationOptionCategoryProps> | ReactElement<NavigationOptionProps> | null | undefined | false;
|
|
8
|
+
export type NavigationChildren = NavigationChild | NavigationChild[];
|
|
8
9
|
export interface NavigationProps extends Omit<NativeElementPropsWithoutKeyAndRef<'ul'>, 'onClick'> {
|
|
9
10
|
/**
|
|
10
11
|
* Current active key.
|
|
@@ -14,6 +15,19 @@ export interface NavigationProps extends Omit<NativeElementPropsWithoutKeyAndRef
|
|
|
14
15
|
* Strict children with `NavigationOption`, `NavigationHeader` or `NavigationFooter`.
|
|
15
16
|
*/
|
|
16
17
|
children?: NavigationChildren;
|
|
18
|
+
/**
|
|
19
|
+
* Navigation display type.
|
|
20
|
+
* @default false (expanded)
|
|
21
|
+
*/
|
|
22
|
+
collapsed?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Whether to show search input
|
|
25
|
+
*/
|
|
26
|
+
filter?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Called when collapsed state changes.
|
|
29
|
+
*/
|
|
30
|
+
onCollapseChange?: (collapsed: boolean) => void;
|
|
17
31
|
/**
|
|
18
32
|
* Called when a navigation option is clicked.
|
|
19
33
|
*/
|
package/Navigation/Navigation.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef, useState, useCallback, useMemo, Children, isValidElement } from 'react';
|
|
3
4
|
import { navigationClasses } from '@mezzanine-ui/core/navigation';
|
|
4
5
|
import NavigationOption from './NavigationOption.js';
|
|
5
6
|
import NavigationHeader from './NavigationHeader.js';
|
|
@@ -8,11 +9,19 @@ import { flattenChildren } from '../utils/flatten-children.js';
|
|
|
8
9
|
import NavigationOptionCategory from './NavigationOptionCategory.js';
|
|
9
10
|
import { NavigationActivatedContext, NavigationOptionLevelContext, navigationOptionLevelContextDefaultValues } from './context.js';
|
|
10
11
|
import { useCurrentPathname } from './useCurrentPathname.js';
|
|
12
|
+
import { useVisibleItems } from './useVisibleItems.js';
|
|
13
|
+
import { CollapsedMenu } from './CollapsedMenu.js';
|
|
11
14
|
import Input from '../Input/Input.js';
|
|
12
15
|
import cx from 'clsx';
|
|
13
16
|
|
|
14
17
|
const Navigation = forwardRef((props, ref) => {
|
|
15
|
-
const { activatedPath, children = [], className, onOptionClick, ...rest } = props;
|
|
18
|
+
const { activatedPath, children = [], className, collapsed: collapsedProp, filter, onCollapseChange, onOptionClick, ...rest } = props;
|
|
19
|
+
const [collapsedState, setCollapsedState] = useState(collapsedProp || false);
|
|
20
|
+
const collapsed = collapsedProp !== null && collapsedProp !== void 0 ? collapsedProp : collapsedState;
|
|
21
|
+
const handleCollapseChange = useCallback((newCollapsed) => {
|
|
22
|
+
setCollapsedState(newCollapsed);
|
|
23
|
+
onCollapseChange === null || onCollapseChange === void 0 ? void 0 : onCollapseChange(newCollapsed);
|
|
24
|
+
}, [onCollapseChange]);
|
|
16
25
|
const [innerActivatedPath, setInnerActivatedPath] = useState([]);
|
|
17
26
|
const combineSetActivatedPath = useCallback((newActivatedPath) => {
|
|
18
27
|
onOptionClick === null || onOptionClick === void 0 ? void 0 : onOptionClick(newActivatedPath);
|
|
@@ -20,11 +29,13 @@ const Navigation = forwardRef((props, ref) => {
|
|
|
20
29
|
}, [onOptionClick]);
|
|
21
30
|
const currentPathname = useCurrentPathname();
|
|
22
31
|
const flattenedChildren = useMemo(() => flattenChildren(children), [children]);
|
|
23
|
-
const
|
|
32
|
+
const [filterText, setFilterText] = useState('');
|
|
33
|
+
const { headerComponent, footerComponent, items, level1Items } = useMemo(() => {
|
|
24
34
|
let headerComponent = null;
|
|
25
35
|
let footerComponent = null;
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
const items = [];
|
|
37
|
+
const level1Items = [];
|
|
38
|
+
Children.forEach(flattenedChildren, (child, index) => {
|
|
28
39
|
if (child && isValidElement(child)) {
|
|
29
40
|
switch (child.type) {
|
|
30
41
|
case NavigationHeader: {
|
|
@@ -35,40 +46,44 @@ const Navigation = forwardRef((props, ref) => {
|
|
|
35
46
|
footerComponent = child;
|
|
36
47
|
break;
|
|
37
48
|
}
|
|
38
|
-
case Input: {
|
|
39
|
-
searchInput = cloneElement(child, {
|
|
40
|
-
size: 'sub',
|
|
41
|
-
variant: 'search',
|
|
42
|
-
});
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
return { headerComponent, footerComponent, searchInput };
|
|
49
|
-
}, [flattenedChildren]);
|
|
50
|
-
const renderItemChildren = useCallback(function renderItemChildrenImpl(parsedChildren) {
|
|
51
|
-
var _a;
|
|
52
|
-
const childArray = Children.map(parsedChildren, (child) => {
|
|
53
|
-
if (child && isValidElement(child)) {
|
|
54
|
-
switch (child.type) {
|
|
55
49
|
case NavigationOptionCategory:
|
|
50
|
+
level1Items.push(...(child.props.children
|
|
51
|
+
? flattenChildren(child.props.children, -1, [
|
|
52
|
+
child.props.title || 'NavigationOptionCategory:' + index,
|
|
53
|
+
])
|
|
54
|
+
: []));
|
|
55
|
+
items.push(child);
|
|
56
|
+
break;
|
|
56
57
|
case NavigationOption: {
|
|
57
|
-
|
|
58
|
+
level1Items.push(child);
|
|
59
|
+
items.push(child);
|
|
60
|
+
break;
|
|
58
61
|
}
|
|
59
62
|
default:
|
|
60
|
-
|
|
63
|
+
console.warn('[Mezzanine][Navigation]: Navigation only accepts NavigationOption, NavigationOptionCategory, NavigationHeader or NavigationFooter as children.');
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
|
-
return null;
|
|
64
66
|
});
|
|
65
|
-
return
|
|
66
|
-
}, []);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
return { headerComponent, footerComponent, items, level1Items };
|
|
68
|
+
}, [flattenedChildren]);
|
|
69
|
+
const { contentRef, visibleCount } = useVisibleItems(items, collapsed);
|
|
70
|
+
const { collapsedItems, collapsedMenuItems } = useMemo(() => {
|
|
71
|
+
return {
|
|
72
|
+
collapsedItems: visibleCount !== null
|
|
73
|
+
? level1Items.slice(0, visibleCount)
|
|
74
|
+
: level1Items,
|
|
75
|
+
collapsedMenuItems: visibleCount !== null ? level1Items.slice(visibleCount) : [],
|
|
76
|
+
};
|
|
77
|
+
}, [level1Items, visibleCount]);
|
|
78
|
+
return (jsx("nav", { ...rest, ref: ref, className: cx(navigationClasses.host, collapsed ? navigationClasses.collapsed : navigationClasses.expand, className), children: jsxs(NavigationActivatedContext.Provider, { value: {
|
|
79
|
+
activatedPath: activatedPath || innerActivatedPath,
|
|
80
|
+
setActivatedPath: combineSetActivatedPath,
|
|
81
|
+
currentPathname,
|
|
82
|
+
collapsed,
|
|
83
|
+
handleCollapseChange,
|
|
84
|
+
}, children: [headerComponent, jsx(NavigationOptionLevelContext.Provider, { value: navigationOptionLevelContextDefaultValues, children: jsxs("div", { ref: contentRef, className: navigationClasses.content, children: [filter && (jsx(Input, { size: "sub", variant: "search", className: cx(navigationClasses.searchInput), value: filterText, onChange: (e) => setFilterText(e.target.value) })), jsxs("ul", { children: [collapsed ? collapsedItems : items, collapsed &&
|
|
85
|
+
visibleCount !== null &&
|
|
86
|
+
visibleCount < level1Items.length && (jsx(CollapsedMenu, { items: collapsedMenuItems }))] }, collapsed ? 'collapsed' : 'expand')] }) }), footerComponent] }) }));
|
|
72
87
|
});
|
|
73
88
|
|
|
74
89
|
export { Navigation as default };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { forwardRef, Children, isValidElement } from 'react';
|
|
2
|
+
import { forwardRef, use, Children, isValidElement } from 'react';
|
|
3
3
|
import { navigationFooterClasses } from '@mezzanine-ui/core/navigation';
|
|
4
4
|
import NavigationUserMenu from './NavigationUserMenu.js';
|
|
5
|
+
import { NavigationActivatedContext } from './context.js';
|
|
5
6
|
import cx from 'clsx';
|
|
6
7
|
|
|
7
8
|
const resolveChildren = (children) => {
|
|
@@ -19,8 +20,9 @@ const resolveChildren = (children) => {
|
|
|
19
20
|
};
|
|
20
21
|
const NavigationFooter = forwardRef((props, ref) => {
|
|
21
22
|
const { children, className, ...rest } = props;
|
|
23
|
+
const { collapsed } = use(NavigationActivatedContext);
|
|
22
24
|
const { userMenu, otherChildren } = resolveChildren(children);
|
|
23
|
-
return (jsxs("footer", { ...rest, ref: ref, className: cx(navigationFooterClasses.host, className), children: [userMenu, jsx("span", { className: navigationFooterClasses.icons, children: otherChildren })] }));
|
|
25
|
+
return (jsxs("footer", { ...rest, ref: ref, className: cx(navigationFooterClasses.host, collapsed && navigationFooterClasses.collapsed, className), children: [userMenu, jsx("span", { className: navigationFooterClasses.icons, children: otherChildren })] }));
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
export { NavigationFooter as default };
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
3
3
|
export interface NavigationHeaderProps extends NativeElementPropsWithoutKeyAndRef<'header'> {
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Custom content to render inside the header, typically an icon or logo.
|
|
6
|
+
*/
|
|
5
7
|
children?: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* The title text displayed in the header.
|
|
10
|
+
*/
|
|
11
|
+
title: string;
|
|
12
|
+
/**
|
|
13
|
+
* Callback function invoked when the brand area (logo and title) is clicked.
|
|
14
|
+
*/
|
|
15
|
+
onBrandClick?: () => void;
|
|
6
16
|
}
|
|
7
17
|
declare const NavigationHeader: import("react").ForwardRefExoticComponent<NavigationHeaderProps & import("react").RefAttributes<HTMLElement>>;
|
|
8
18
|
export default NavigationHeader;
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { forwardRef } from 'react';
|
|
2
|
+
import { forwardRef, use } from 'react';
|
|
3
3
|
import { navigationHeaderClasses } from '@mezzanine-ui/core/navigation';
|
|
4
4
|
import { SiderIcon } from '@mezzanine-ui/icons';
|
|
5
5
|
import NavigationIconButton from './NavigationIconButton.js';
|
|
6
|
+
import { NavigationActivatedContext } from './context.js';
|
|
6
7
|
import cx from 'clsx';
|
|
7
8
|
|
|
8
9
|
const NavigationHeader = forwardRef((props, ref) => {
|
|
9
|
-
const { children, className,
|
|
10
|
-
|
|
10
|
+
const { children, className, title, onBrandClick, ...rest } = props;
|
|
11
|
+
const { collapsed, handleCollapseChange } = use(NavigationActivatedContext);
|
|
12
|
+
const BrandComponent = onBrandClick ? 'button' : 'span';
|
|
13
|
+
return (jsxs("header", { ...rest, ref: ref, className: cx(navigationHeaderClasses.host, collapsed && navigationHeaderClasses.collapsed, className), children: [jsx(NavigationIconButton, { onClick: () => handleCollapseChange(!collapsed), icon: SiderIcon }), jsxs(BrandComponent, { type: "button", className: navigationHeaderClasses.content, onClick: onBrandClick, children: [children, jsx("span", { className: navigationHeaderClasses.title, children: title })] })] }));
|
|
11
14
|
});
|
|
12
15
|
|
|
13
16
|
export { NavigationHeader as default };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
2
|
import { IconDefinition } from '@mezzanine-ui/icons';
|
|
3
3
|
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
4
|
-
|
|
4
|
+
import { BadgeProps } from '../Badge';
|
|
5
|
+
export type NavigationOptionChild = ReactElement<NavigationOptionProps> | ReactElement<BadgeProps> | false | null | undefined;
|
|
5
6
|
export type NavigationOptionChildren = NavigationOptionChild | NavigationOptionChild[];
|
|
6
|
-
export interface NavigationOptionProps extends Omit<NativeElementPropsWithoutKeyAndRef<'li'>, 'onClick'> {
|
|
7
|
+
export interface NavigationOptionProps extends Omit<NativeElementPropsWithoutKeyAndRef<'li'>, 'onClick' | 'onMouseEnter' | 'onMouseLeave'> {
|
|
7
8
|
/**
|
|
8
9
|
* Whether the item is active.
|
|
9
10
|
*/
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
-
import { forwardRef, useState,
|
|
3
|
+
import { forwardRef, useState, use, useMemo, useEffect, Children, isValidElement } from 'react';
|
|
4
4
|
import { navigationOptionClasses } from '@mezzanine-ui/core/navigation';
|
|
5
5
|
import { ChevronUpIcon, ChevronDownIcon } from '@mezzanine-ui/icons';
|
|
6
|
-
import { useClickAway } from '../hooks/useClickAway.js';
|
|
7
|
-
import { useComposeRefs } from '../hooks/useComposeRefs.js';
|
|
8
6
|
import { NavigationOptionLevelContext, NavigationActivatedContext } from './context.js';
|
|
7
|
+
import { flattenChildren } from '../utils/flatten-children.js';
|
|
8
|
+
import Badge from '../Badge/Badge.js';
|
|
9
|
+
import Tooltip from '../Tooltip/Tooltip.js';
|
|
9
10
|
import Icon from '../Icon/Icon.js';
|
|
10
11
|
import Collapse from '../Transition/Collapse.js';
|
|
11
12
|
import cx from 'clsx';
|
|
@@ -13,48 +14,66 @@ import cx from 'clsx';
|
|
|
13
14
|
const NavigationOption = forwardRef((props, ref) => {
|
|
14
15
|
const { active, children, className, defaultOpen = false, href, icon, onTriggerClick, title, ...rest } = props;
|
|
15
16
|
const [open, setOpen] = useState(defaultOpen);
|
|
16
|
-
const nodeRef = useRef(null);
|
|
17
|
-
const composedNodeRef = useComposeRefs([ref, nodeRef]);
|
|
18
17
|
const GroupToggleIcon = open ? ChevronUpIcon : ChevronDownIcon;
|
|
19
|
-
useClickAway(() => {
|
|
20
|
-
if (!open) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
return () => {
|
|
24
|
-
setOpen(!open);
|
|
25
|
-
};
|
|
26
|
-
}, nodeRef, [open]);
|
|
27
18
|
const { level, path: parentPath } = use(NavigationOptionLevelContext);
|
|
28
19
|
const currentLevel = level + 1;
|
|
29
20
|
const currentKey = href || title || 'unknownId';
|
|
30
21
|
const currentPath = useMemo(() => [...parentPath, currentKey], [parentPath, currentKey]);
|
|
31
|
-
const { activatedPath, setActivatedPath, currentPathname } = use(NavigationActivatedContext);
|
|
22
|
+
const { activatedPath, setActivatedPath, currentPathname, collapsed, handleCollapseChange, } = use(NavigationActivatedContext);
|
|
32
23
|
useEffect(() => {
|
|
33
24
|
if (currentPathname === href) {
|
|
34
25
|
setActivatedPath(currentPath);
|
|
26
|
+
setOpen(true);
|
|
35
27
|
}
|
|
36
28
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37
29
|
}, []);
|
|
38
30
|
const Component = href ? 'a' : 'div';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
31
|
+
const flattenedChildren = useMemo(() => flattenChildren(children), [children]);
|
|
32
|
+
const { badge, items } = useMemo(() => {
|
|
33
|
+
let badgeComponent = null;
|
|
34
|
+
const items = [];
|
|
35
|
+
Children.forEach(flattenedChildren, (child) => {
|
|
36
|
+
if (child && isValidElement(child)) {
|
|
37
|
+
switch (child.type) {
|
|
38
|
+
case Badge: {
|
|
39
|
+
badgeComponent = child;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case NavigationOption: {
|
|
43
|
+
items.push(child);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
default:
|
|
47
|
+
console.warn('[Mezzanine][NavigationOption]: NavigationOption only accepts NavigationOption or Badge as children.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return { badge: badgeComponent, items };
|
|
52
|
+
}, [flattenedChildren]);
|
|
53
|
+
return (jsxs("li", { ...rest, ref: ref, className: cx(navigationOptionClasses.host, open && navigationOptionClasses.open, !children && navigationOptionClasses.basic, (active !== null && active !== void 0 ? active : (activatedPath === null || activatedPath === void 0 ? void 0 : activatedPath[currentLevel - 1]) === currentKey) &&
|
|
54
|
+
navigationOptionClasses.active, collapsed && navigationOptionClasses.collapsed, className), "data-id": currentKey, children: [jsx(Tooltip, { options: {
|
|
55
|
+
placement: 'right',
|
|
56
|
+
}, title: collapsed ? title : undefined, children: ({ onMouseEnter, onMouseLeave, ref: tooltipChildRef }) => (jsxs(Component, { className: cx(navigationOptionClasses.content, navigationOptionClasses.level(currentLevel)), onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, ref: tooltipChildRef, href: href, onClick: () => {
|
|
48
57
|
setOpen(!open);
|
|
58
|
+
onTriggerClick === null || onTriggerClick === void 0 ? void 0 : onTriggerClick(currentPath, href || '');
|
|
59
|
+
if (collapsed) {
|
|
60
|
+
handleCollapseChange(false);
|
|
61
|
+
}
|
|
49
62
|
if (!children)
|
|
50
63
|
setActivatedPath([...parentPath, currentKey]);
|
|
51
|
-
}
|
|
52
|
-
|
|
64
|
+
}, onKeyDown: (e) => {
|
|
65
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
setOpen(!open);
|
|
68
|
+
if (!children)
|
|
69
|
+
setActivatedPath([...parentPath, currentKey]);
|
|
70
|
+
}
|
|
71
|
+
}, role: "menuitem", tabIndex: 0, children: [icon && jsx(Icon, { className: navigationOptionClasses.icon, icon: icon }), jsx("span", { className: navigationOptionClasses.title, children: title }), badge, children && (jsx(Icon, { className: navigationOptionClasses.toggleIcon, icon: GroupToggleIcon }))] })) }), children && !collapsed && (jsx(Collapse, { className: navigationOptionClasses.childrenWrapper, style: {
|
|
53
72
|
width: '100%',
|
|
54
73
|
}, in: !!open, children: jsx(NavigationOptionLevelContext.Provider, { value: {
|
|
55
74
|
level: currentLevel,
|
|
56
75
|
path: currentPath,
|
|
57
|
-
}, children: jsx("ul", { className: navigationOptionClasses.group, children:
|
|
76
|
+
}, children: jsx("ul", { className: navigationOptionClasses.group, children: items }) }) }))] }));
|
|
58
77
|
});
|
|
59
78
|
|
|
60
79
|
export { NavigationOption as default };
|
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
-
import { forwardRef } from 'react';
|
|
3
|
+
import { forwardRef, useCallback, Children, isValidElement } from 'react';
|
|
4
4
|
import { navigationOptionCategoryClasses } from '@mezzanine-ui/core/navigation';
|
|
5
|
+
import NavigationOption from './NavigationOption.js';
|
|
5
6
|
import cx from 'clsx';
|
|
6
7
|
|
|
7
8
|
const NavigationOptionCategory = forwardRef((props, ref) => {
|
|
8
9
|
const { children, className, title, ...rest } = props;
|
|
9
|
-
|
|
10
|
+
const renderItemChildren = useCallback(function renderItemChildrenImpl(parsedChildren) {
|
|
11
|
+
var _a;
|
|
12
|
+
const childArray = Children.map(parsedChildren, (child) => {
|
|
13
|
+
if (child && isValidElement(child)) {
|
|
14
|
+
switch (child.type) {
|
|
15
|
+
case NavigationOption: {
|
|
16
|
+
return child;
|
|
17
|
+
}
|
|
18
|
+
default:
|
|
19
|
+
console.warn('[Mezzanine][NavigationOptionCategory]: NavigationOptionCategory only accepts NavigationOption as children.');
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
});
|
|
25
|
+
return (_a = childArray === null || childArray === void 0 ? void 0 : childArray.filter((child) => child !== null)) !== null && _a !== void 0 ? _a : null;
|
|
26
|
+
}, []);
|
|
27
|
+
return (jsxs("li", { ...rest, ref: ref, className: cx(navigationOptionCategoryClasses.host, className), role: "menuitem", children: [jsx("span", { className: navigationOptionCategoryClasses.title, children: title }), jsx("ul", { children: renderItemChildren(children) })] }));
|
|
10
28
|
});
|
|
11
29
|
|
|
12
30
|
export { NavigationOptionCategory as default };
|
package/Navigation/context.d.ts
CHANGED
|
@@ -10,4 +10,6 @@ export declare const NavigationActivatedContext: import("react").Context<{
|
|
|
10
10
|
activatedPath: string[];
|
|
11
11
|
setActivatedPath: (path: string[]) => void;
|
|
12
12
|
currentPathname: string | null;
|
|
13
|
+
collapsed: boolean;
|
|
14
|
+
handleCollapseChange: (newCollapsed: boolean) => void;
|
|
13
15
|
}>;
|