devextreme-cli 1.11.0-alpha.0 → 1.11.0-beta.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.
Files changed (72) hide show
  1. package/package.json +4 -3
  2. package/src/application.js +40 -20
  3. package/src/applications/application.angular.js +11 -3
  4. package/src/applications/application.nextjs.js +231 -0
  5. package/src/applications/application.react.js +15 -5
  6. package/src/templates/nextjs/application/.env +1 -0
  7. package/src/templates/nextjs/application/devextreme.json +63 -0
  8. package/src/templates/nextjs/application/next.config.mjs +32 -0
  9. package/src/templates/nextjs/application/public/logo192.png +0 -0
  10. package/src/templates/nextjs/application/public/logo512.png +0 -0
  11. package/src/templates/nextjs/application/public/manifest.json +25 -0
  12. package/src/templates/nextjs/application/public/robots.txt +3 -0
  13. package/src/templates/nextjs/application/src/app/actions/auth.ts +76 -0
  14. package/src/templates/nextjs/application/src/app/auth/[type]/page.tsx +49 -0
  15. package/src/templates/nextjs/application/src/app/layout.tsx +17 -0
  16. package/src/templates/nextjs/application/src/app/lib/session.ts +47 -0
  17. package/src/templates/nextjs/application/src/app/pages/layout.tsx +18 -0
  18. package/src/templates/nextjs/application/src/app-info.tsx +5 -0
  19. package/src/templates/nextjs/application/src/app-navigation.tsx +21 -0
  20. package/src/templates/nextjs/application/src/components/change-password-form/ChangePasswordForm.tsx +86 -0
  21. package/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.scss +19 -0
  22. package/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.tsx +107 -0
  23. package/src/templates/nextjs/application/src/components/footer/Footer.scss +12 -0
  24. package/src/templates/nextjs/application/src/components/footer/Footer.tsx +5 -0
  25. package/src/templates/nextjs/application/src/components/header/Header.scss +40 -0
  26. package/src/templates/nextjs/application/src/components/header/Header.tsx +38 -0
  27. package/src/templates/nextjs/application/src/components/index.tsx +7 -0
  28. package/src/templates/nextjs/application/src/components/login-form/LoginForm.scss +12 -0
  29. package/src/templates/nextjs/application/src/components/login-form/LoginForm.tsx +101 -0
  30. package/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.scss +12 -0
  31. package/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.tsx +78 -0
  32. package/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.scss +71 -0
  33. package/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.tsx +88 -0
  34. package/src/templates/nextjs/application/src/components/theme-switcher/ThemeSwitcher.tsx +21 -0
  35. package/src/templates/nextjs/application/src/components/user-panel/UserPanel.scss +51 -0
  36. package/src/templates/nextjs/application/src/components/user-panel/UserPanel.tsx +55 -0
  37. package/src/templates/nextjs/application/src/dx-styles.scss +106 -0
  38. package/src/templates/nextjs/application/src/index.css +12 -0
  39. package/src/templates/nextjs/application/src/layouts/index.tsx +3 -0
  40. package/src/templates/nextjs/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.scss +17 -0
  41. package/src/templates/nextjs/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx +133 -0
  42. package/src/templates/nextjs/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.scss +10 -0
  43. package/src/templates/nextjs/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx +119 -0
  44. package/src/templates/nextjs/application/src/layouts/single-card/single-card.scss +42 -0
  45. package/src/templates/nextjs/application/src/layouts/single-card/single-card.tsx +16 -0
  46. package/src/templates/nextjs/application/src/middleware.ts +46 -0
  47. package/src/templates/nextjs/application/src/theme.tsx +66 -0
  48. package/src/templates/nextjs/application/src/themes/metadata.additional.dark.json +11 -0
  49. package/src/templates/nextjs/application/src/themes/metadata.additional.json +11 -0
  50. package/src/templates/nextjs/application/src/themes/metadata.base.dark.json +8 -0
  51. package/src/templates/nextjs/application/src/themes/metadata.base.json +7 -0
  52. package/src/templates/nextjs/application/src/types.tsx +60 -0
  53. package/src/templates/nextjs/application/src/utils/default-user.tsx +7 -0
  54. package/src/templates/nextjs/application/src/utils/media-query.tsx +56 -0
  55. package/src/templates/nextjs/application/src/variables.scss +53 -0
  56. package/src/templates/nextjs/page/page.scss +0 -0
  57. package/src/templates/nextjs/page/page.tsx +13 -0
  58. package/src/templates/nextjs/sample-pages/home/home.scss +37 -0
  59. package/src/templates/nextjs/sample-pages/home/page.tsx +101 -0
  60. package/src/templates/nextjs/sample-pages/profile/page.tsx +61 -0
  61. package/src/templates/nextjs/sample-pages/profile/profile.scss +19 -0
  62. package/src/templates/nextjs/sample-pages/tasks/page.tsx +112 -0
  63. package/src/templates/nextjs/sample-pages/tasks/tasks.scss +3 -0
  64. package/src/templates/react/application/src/components/header/Header.scss +1 -1
  65. package/src/templates/react/application/src/dx-styles.scss +3 -3
  66. package/src/templates/vue-v3/application/src/components/header-toolbar.vue +1 -1
  67. package/src/templates/vue-v3/application/src/dx-styles.scss +4 -3
  68. package/src/utility/latest-versions.js +6 -4
  69. package/src/utility/module.js +11 -3
  70. package/src/utility/prompts/react-app-type.js +17 -0
  71. package/src/utility/run-command.js +10 -2
  72. package/src/utility/template-creator.js +8 -4
@@ -0,0 +1,78 @@
1
+ 'use client'
2
+ import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
3
+ import { useRouter } from 'next/navigation';
4
+ import Link from 'next/link';
5
+ import Form, {
6
+ Item,
7
+ Label,
8
+ ButtonItem,
9
+ ButtonOptions,
10
+ RequiredRule,
11
+ EmailRule
12
+ } from 'devextreme-react/form';
13
+ import LoadIndicator from 'devextreme-react/load-indicator';
14
+ import notify from 'devextreme/ui/notify';
15
+ import { resetPassword } from '@/app/actions/auth';
16
+ import './ResetPasswordForm.scss';
17
+
18
+ const notificationText = 'We\'ve sent a link to reset your password. Check your inbox.';
19
+
20
+ export default function ResetPasswordForm() {
21
+ const router = useRouter();
22
+ const [loading, setLoading] = useState(false);
23
+ const formData = useRef({ email: '', password: '' });
24
+
25
+ const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<HTMLFormElement><%=/isTypeScript%>) => {
26
+ e.preventDefault();
27
+ const { email } = formData.current;
28
+ setLoading(true);
29
+
30
+ const result = await resetPassword(email);
31
+ setLoading(false);
32
+
33
+ if (result.isOk) {
34
+ router.push('/login');
35
+ notify(notificationText, 'success', 2500);
36
+ } else {
37
+ notify(result.message, 'error', 2000);
38
+ }
39
+ }, [router]);
40
+
41
+ return (
42
+ <form className={'reset-password-form'} onSubmit={onSubmit}>
43
+ <Form formData={formData.current} disabled={loading}>
44
+ <Item
45
+ dataField={'email'}
46
+ editorType={'dxTextBox'}
47
+ editorOptions={emailEditorOptions}
48
+ >
49
+ <RequiredRule message="Email is required" />
50
+ <EmailRule message="Email is invalid" />
51
+ <Label visible={false} />
52
+ </Item>
53
+ <ButtonItem>
54
+ <ButtonOptions
55
+ elementAttr={submitButtonAttributes}
56
+ width={'100%'}
57
+ type={'default'}
58
+ useSubmitBehavior={true}
59
+ >
60
+ <span className="dx-button-text">
61
+ {
62
+ loading
63
+ ? <LoadIndicator width={'24px'} height={'24px'} visible={true} />
64
+ : 'Reset my password'
65
+ }
66
+ </span>
67
+ </ButtonOptions>
68
+ </ButtonItem>
69
+ </Form>
70
+ <div className={'login-link'}>
71
+ Return to <Link href={'/login'}>Sign In</Link>
72
+ </div>
73
+ </form>
74
+ );
75
+ }
76
+
77
+ const emailEditorOptions = { stylingMode: 'filled', placeholder: 'Email', mode: 'email' };
78
+ const submitButtonAttributes = { class: 'submit-button' };
@@ -0,0 +1,71 @@
1
+ @use "../../dx-styles" as *;
2
+
3
+ .dx-swatch-additional, .dx-swatch-additional-dark {
4
+ &.side-navigation-menu {
5
+ display: flex;
6
+ flex-direction: column;
7
+ min-height: 100%;
8
+ height: 100%;
9
+ width: 250px !important;
10
+ background-color: var(--base-bg);
11
+
12
+ .menu-container {
13
+ min-height: 100%;
14
+ display: flex;
15
+ flex: 1;
16
+
17
+ .dx-treeview {
18
+ // ## Long text positioning
19
+ white-space: nowrap;
20
+ // ##
21
+
22
+ .dx-treeview-node-container:empty {
23
+ display: none;
24
+ }
25
+
26
+ // ## Icon width customization
27
+ .dx-treeview-item {
28
+ padding-left: 0;
29
+ border-radius: 0;
30
+ flex-direction: row-reverse;
31
+
32
+ .dx-icon {
33
+ width: $side-panel-min-width !important;
34
+ margin: 0 !important;
35
+ }
36
+ }
37
+
38
+ // ##
39
+
40
+ // ## Arrow customization
41
+ .dx-treeview-node {
42
+ padding: 0 0 !important;
43
+ }
44
+
45
+ .dx-treeview-toggle-item-visibility {
46
+ right: 10px;
47
+ left: auto;
48
+ }
49
+
50
+ .dx-rtl .dx-treeview-toggle-item-visibility {
51
+ left: 10px;
52
+ right: auto;
53
+ }
54
+ // ##
55
+
56
+ // ## Item levels customization
57
+ .dx-treeview-node {
58
+ &[aria-level="1"] {
59
+ font-weight: bold;
60
+ }
61
+
62
+ &[aria-level="2"] .dx-treeview-item-content {
63
+ font-weight: normal;
64
+ padding: 0 $side-panel-min-width;
65
+ }
66
+ }
67
+ // ##
68
+ }
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,88 @@
1
+ 'use client'
2
+ import React, { useEffect, useRef, useCallback, useMemo, useContext } from 'react';
3
+ import { TreeView<%=#isTypeScript%>, TreeViewRef<%=/isTypeScript%> } from 'devextreme-react/tree-view';
4
+ import * as events from 'devextreme-react/common/core/events';
5
+ import { navigation } from '@/app-navigation';
6
+ import { usePathname } from 'next/navigation';
7
+ import { useScreenSize } from '@/utils/media-query';
8
+ import './SideNavigationMenu.scss';
9
+ <%=#isTypeScript%>import type { SideNavigationMenuProps } from '@/types';
10
+ <%=/isTypeScript%>import { ThemeContext } from '@/theme';
11
+
12
+ export default function SideNavigationMenu(props<%=#isTypeScript%>: React.PropsWithChildren<SideNavigationMenuProps><%=/isTypeScript%>) {
13
+ const {
14
+ children,
15
+ selectedItemChanged,
16
+ openMenu,
17
+ compactMode,
18
+ onMenuReady
19
+ } = props;
20
+
21
+ const theme = useContext(ThemeContext);
22
+ const { isLarge } = useScreenSize();
23
+ function normalizePath () {
24
+ return navigation.map((item) => (
25
+ { ...item, expanded: isLarge, path: item.path && !(/^\//.test(item.path)) ? `/${item.path}` : item.path }
26
+ ))
27
+ }
28
+
29
+ const items = useMemo(
30
+ normalizePath,
31
+ // eslint-disable-next-line react-hooks/exhaustive-deps
32
+ []
33
+ );
34
+
35
+ const pathname = usePathname();
36
+
37
+ const treeViewRef = useRef<%=#isTypeScript%><TreeViewRef><%=/isTypeScript%>(null);
38
+ const wrapperRef = useRef<%=#isTypeScript%><HTMLDivElement><%=/isTypeScript%>(null);
39
+ const getWrapperRef = useCallback((element<%=#isTypeScript%>: HTMLDivElement<%=/isTypeScript%>) => {
40
+ const prevElement = wrapperRef.current;
41
+ if (prevElement) {
42
+ events.off(prevElement, 'dxclick');
43
+ }
44
+
45
+ wrapperRef.current = element;
46
+ events.on(element, 'dxclick', (e<%=#isTypeScript%>: React.PointerEvent<%=/isTypeScript%>) => {
47
+ openMenu(e);
48
+ });
49
+ }, [openMenu]);
50
+
51
+ useEffect(() => {
52
+ const treeView = treeViewRef.current && treeViewRef.current.instance();
53
+ if (!treeView) {
54
+ return;
55
+ }
56
+
57
+ if (pathname !== undefined) {
58
+ treeView.selectItem(pathname);
59
+ treeView.expandItem(pathname);
60
+ }
61
+
62
+ if (compactMode) {
63
+ treeView.collapseAll();
64
+ }
65
+ }, [pathname, compactMode]);
66
+
67
+ return (
68
+ <div
69
+ className={`dx-swatch-additional${theme?.isDark() ? '-dark' : ''} side-navigation-menu`}
70
+ ref={getWrapperRef}
71
+ >
72
+ {children}
73
+ <div className={'menu-container'}>
74
+ <TreeView
75
+ ref={treeViewRef}
76
+ items={items}
77
+ keyExpr={'path'}
78
+ selectionMode={'single'}
79
+ focusStateEnabled={false}
80
+ expandEvent={'click'}
81
+ onItemClick={selectedItemChanged}
82
+ onContentReady={onMenuReady}
83
+ width={'100%'}
84
+ />
85
+ </div>
86
+ </div>
87
+ );
88
+ }
@@ -0,0 +1,21 @@
1
+ 'use client'
2
+ import { useCallback, useContext } from 'react';
3
+ import Button from 'devextreme-react/button';
4
+ import { ThemeContext } from '@/theme';
5
+
6
+ export const ThemeSwitcher = () => {
7
+ const themeContext = useContext(ThemeContext);
8
+
9
+ const onButtonClick = useCallback(() => {
10
+ themeContext?.switchTheme();
11
+ }, [themeContext]);
12
+
13
+ return <div>
14
+ <Button
15
+ className='theme-button'
16
+ stylingMode='text'
17
+ icon={`${themeContext?.theme === 'dark' ? 'sun' : 'moon'}`}
18
+ onClick={onButtonClick}
19
+ />
20
+ </div>;
21
+ };
@@ -0,0 +1,51 @@
1
+ .app .header-toolbar .user-panel .user-button.dx-dropdownbutton img.dx-icon {
2
+ height: 100%;
3
+ width: auto;
4
+
5
+ .dx-theme-generic & {
6
+ max-height: 32px;
7
+ }
8
+ }
9
+
10
+ .user-panel {
11
+ display: flex;
12
+ flex-direction: column;
13
+
14
+ .user-button.dx-dropdownbutton {
15
+ margin-left: 5px;
16
+
17
+ img.dx-icon {
18
+ border-radius: 50%;
19
+ margin: 0;
20
+ width: auto;
21
+ aspect-ratio: 1 / 1;
22
+ box-sizing: border-box;
23
+ border: 1px solid var(--dx-color-border);
24
+ object-fit: cover;
25
+ object-position: top;
26
+ background: rgb(255, 255, 255);
27
+ background-clip: padding-box;
28
+ }
29
+
30
+
31
+
32
+ .dx-buttongroup {
33
+ vertical-align: middle;
34
+
35
+ .dx-button.dx-button-has-icon:not(.dx-button-has-text) {
36
+ .dx-button-content {
37
+ padding: 0;
38
+ }
39
+
40
+ &.dx-state-hover,
41
+ &.dx-state-focused {
42
+ background-color: transparent;
43
+
44
+ img.dx-icon {
45
+ border-color: var(--dx-color-primary);
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,55 @@
1
+ 'use client'
2
+ import { useMemo, useCallback } from 'react';
3
+ import { useRouter } from 'next/navigation';
4
+ import DropDownButton from 'devextreme-react/drop-down-button';
5
+ import List from 'devextreme-react/list';
6
+ import { signOut } from '@/app/actions/auth';
7
+ import './UserPanel.scss';
8
+ <%=#isTypeScript%>import type { UserPanelProps } from '@/types';<%=/isTypeScript%>
9
+
10
+ export default function UserPanel({ menuMode }<%=#isTypeScript%>: UserPanelProps<%=/isTypeScript%>) {
11
+ const router = useRouter();
12
+
13
+ const navigateToProfile = useCallback(() => {
14
+ router.push("/pages/profile");
15
+ }, [router]);
16
+
17
+ const menuItems = useMemo(() => ([
18
+ {
19
+ text: 'Profile',
20
+ icon: 'user',
21
+ onClick: navigateToProfile
22
+ },
23
+ {
24
+ text: 'Logout',
25
+ icon: 'runner',
26
+ onClick: signOut
27
+ }
28
+ ]), [navigateToProfile]);
29
+
30
+ const dropDownButtonAttributes = {
31
+ class: 'user-button'
32
+ };
33
+
34
+ const buttonDropDownOptions = {
35
+ width: '150px'
36
+ };
37
+
38
+ return (
39
+ <div className='user-panel'>
40
+ {menuMode === 'context' && (
41
+ <DropDownButton
42
+ stylingMode='text'
43
+ icon='https://js.devexpress.com/Demos/WidgetsGallery/JSDemos/images/employees/06.png'
44
+ showArrowIcon={false}
45
+ elementAttr={dropDownButtonAttributes}
46
+ dropDownOptions={buttonDropDownOptions}
47
+ items={menuItems}>
48
+ </DropDownButton>
49
+ )}
50
+ {menuMode === 'list' && (
51
+ <List items={menuItems} />
52
+ )}
53
+ </div>
54
+ );
55
+ }
@@ -0,0 +1,106 @@
1
+ @use "variables.scss" as *;
2
+
3
+ $side-panel-min-width: 60px;
4
+
5
+ html,
6
+ body {
7
+ margin: 0;
8
+ min-height: 100%;
9
+ height: 100%;
10
+ }
11
+
12
+ .dx-viewport {
13
+ .dx-popup-wrapper {
14
+ z-index: 1510 !important;
15
+ }
16
+
17
+ #root {
18
+ height: 100%;
19
+ }
20
+
21
+ * {
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ .app {
26
+ background-color: var(--base-bg-darken-5);
27
+ display: flex;
28
+ height: 100%;
29
+ width: 100%;
30
+ min-width: 320px;
31
+ }
32
+
33
+ .content {
34
+ line-height: 1.5;
35
+ flex-grow: 1;
36
+ padding: 20px 40px;
37
+
38
+ h2 {
39
+ font-size: 32px;
40
+ margin: 0;
41
+ line-height: 40px;
42
+ }
43
+ }
44
+
45
+ @media (max-width: 599.99px) {
46
+ :not(.dx-card).content {
47
+ padding: 20px;
48
+ }
49
+ }
50
+
51
+ .container {
52
+ height: 100%;
53
+ flex-direction: column;
54
+ display: flex;
55
+ }
56
+
57
+ .layout-body {
58
+ flex: 1;
59
+ min-height: 0;
60
+ }
61
+
62
+ .side-nav-outer-toolbar .dx-drawer {
63
+ height: calc(100% - 56px)
64
+ }
65
+
66
+ .content-block {
67
+ margin-top: 20px;
68
+ }
69
+
70
+ .responsive-paddings {
71
+ padding: 20px;
72
+
73
+ @media (min-width: 1280px) {
74
+ padding: 40px;
75
+ }
76
+ }
77
+
78
+ .dx-card.wide-card {
79
+ border-radius: 0;
80
+ margin-left: 0;
81
+ margin-right: 0;
82
+ border-right: 0;
83
+ border-left: 0;
84
+ }
85
+
86
+ .with-footer > .dx-scrollable-wrapper >
87
+ .dx-scrollable-container > .dx-scrollable-content {
88
+ height: 100%;
89
+
90
+ & > .dx-scrollview-content {
91
+ display: flex;
92
+ flex-direction: column;
93
+ min-height: 100%;
94
+ }
95
+ }
96
+
97
+ }
98
+
99
+ .dx-theme-fluent {
100
+ .dx-drawer-wrapper {
101
+ .dx-drawer-panel-content,
102
+ .dx-overlay-content {
103
+ box-shadow: 0 4px 4px 0 var(--shadow-color-first), 0 1px 2px 0 var(--shadow-color-second);
104
+ }
105
+ }
106
+ }
@@ -0,0 +1,12 @@
1
+ body {
2
+ margin: 0;
3
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5
+ sans-serif;
6
+ -moz-osx-font-smoothing: grayscale;
7
+ }
8
+
9
+ code {
10
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
11
+ monospace;
12
+ }
@@ -0,0 +1,3 @@
1
+ export { default as SideNavOuterToolbar } from './side-nav-outer-toolbar/side-nav-outer-toolbar';
2
+ export { default as SideNavInnerToolbar } from './side-nav-inner-toolbar/side-nav-inner-toolbar';
3
+ export { default as SingleCard } from './single-card/single-card';
@@ -0,0 +1,17 @@
1
+ .side-nav-inner-toolbar {
2
+ width: 100%;
3
+ }
4
+
5
+ #navigation-header {
6
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
7
+ background-color: var(--base-bg);
8
+
9
+ @media (max-width: 599.99px) {
10
+ padding-left: 20px;
11
+ }
12
+
13
+ .dx-theme-generic & {
14
+ padding-top: 10px;
15
+ padding-bottom: 10px;
16
+ }
17
+ }
@@ -0,0 +1,133 @@
1
+ 'use client'
2
+ import Button from 'devextreme-react/button';
3
+ import Drawer from 'devextreme-react/drawer';
4
+ import { ScrollView<%=#isTypeScript%>, ScrollViewRef<%=/isTypeScript%> } from 'devextreme-react/scroll-view';
5
+ import Toolbar, { Item } from 'devextreme-react/toolbar';
6
+ import React, { useState, useCallback, useRef } from 'react';
7
+ import { useRouter } from 'next/navigation';
8
+ import { Header, SideNavigationMenu } from '@/components';
9
+ import './side-nav-inner-toolbar.scss';
10
+ import { useScreenSize } from '@/utils/media-query';
11
+ import { Template } from 'devextreme-react/core/template';
12
+ <%=#isTypeScript%>import type { TreeViewTypes } from 'devextreme-react/tree-view';
13
+ import type { SideNavToolbarProps } from '@/types';
14
+ import type { ButtonTypes } from 'devextreme-react/button';
15
+ <%=/isTypeScript%>
16
+ export default function SideNavInnerToolbar({ title, children }<%=#isTypeScript%>: React.PropsWithChildren<SideNavToolbarProps><%=/isTypeScript%>) {
17
+ const scrollViewRef = useRef<%=#isTypeScript%><ScrollViewRef><%=/isTypeScript%>(null);
18
+ const router = useRouter();
19
+ const { isXSmall, isLarge } = useScreenSize();
20
+ const [menuStatus, setMenuStatus] = useState(
21
+ isLarge ? MenuStatus.Opened : MenuStatus.Closed
22
+ );
23
+
24
+ const toggleMenu = useCallback(({ event }<%=#isTypeScript%>: ButtonTypes.ClickEvent<%=/isTypeScript%>) => {
25
+ setMenuStatus(
26
+ prevMenuStatus => prevMenuStatus === MenuStatus.Closed
27
+ ? MenuStatus.Opened
28
+ : MenuStatus.Closed
29
+ );
30
+ event<%=#isTypeScript%>?<%=/isTypeScript%>.stopPropagation();
31
+ }, []);
32
+
33
+ const temporaryOpenMenu = useCallback(() => {
34
+ setMenuStatus(
35
+ prevMenuStatus => prevMenuStatus === MenuStatus.Closed
36
+ ? MenuStatus.TemporaryOpened
37
+ : prevMenuStatus
38
+ );
39
+ }, []);
40
+
41
+ const onOutsideClick = useCallback(() => {
42
+ setMenuStatus(
43
+ prevMenuStatus => prevMenuStatus !== MenuStatus.Closed && !isLarge
44
+ ? MenuStatus.Closed
45
+ : prevMenuStatus
46
+ );
47
+ return menuStatus === MenuStatus.Closed ? true : false;
48
+ }, [isLarge, menuStatus]);
49
+
50
+ const onNavigationChanged = useCallback(({ itemData, event, node }<%=#isTypeScript%>: TreeViewTypes.ItemClickEvent<%=/isTypeScript%>) => {
51
+ if (menuStatus === MenuStatus.Closed || !itemData<%=#isTypeScript%>?<%=/isTypeScript%>.path || node<%=#isTypeScript%>?<%=/isTypeScript%>.selected) {
52
+ event<%=#isTypeScript%>?<%=/isTypeScript%>.preventDefault();
53
+ return;
54
+ }
55
+
56
+ router.push(itemData.path);
57
+ scrollViewRef.current<%=#isTypeScript%>?<%=/isTypeScript%>.instance().scrollTo(0);
58
+
59
+ if (!isLarge || menuStatus === MenuStatus.TemporaryOpened) {
60
+ setMenuStatus(MenuStatus.Closed);
61
+ event<%=#isTypeScript%>?<%=/isTypeScript%>.stopPropagation();
62
+ }
63
+ }, [router, menuStatus, isLarge]);
64
+
65
+ return (
66
+ <div className={'side-nav-inner-toolbar'}>
67
+ <Drawer
68
+ className={'drawer'}
69
+ position={'before'}
70
+ closeOnOutsideClick={onOutsideClick}
71
+ openedStateMode={isLarge ? 'shrink' : 'overlap'}
72
+ revealMode={isXSmall ? 'slide' : 'expand'}
73
+ minSize={isXSmall ? 0 : 60}
74
+ maxSize={250}
75
+ shading={isLarge ? false : true}
76
+ opened={menuStatus === MenuStatus.Closed ? false : true}
77
+ template={'menu'}
78
+ >
79
+ <div className={'container'}>
80
+ <Header
81
+ menuToggleEnabled={isXSmall}
82
+ toggleMenu={toggleMenu}
83
+ />
84
+ <ScrollView ref={scrollViewRef} className={'layout-body with-footer'}>
85
+ <div className={'content'}>
86
+ {React.Children.map(children, (item) => {
87
+ if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type !== "footer") {
88
+ return item;
89
+ }
90
+ return null;
91
+ })}
92
+ </div>
93
+ <div className={'content-block'}>
94
+ {React.Children.map(children, (item) => {
95
+ if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type === "footer") {
96
+ return item;
97
+ }
98
+ return null;
99
+ })}
100
+ </div>
101
+ </ScrollView>
102
+ </div>
103
+ <Template name={'menu'}>
104
+ <SideNavigationMenu
105
+ compactMode={menuStatus === MenuStatus.Closed}
106
+ selectedItemChanged={onNavigationChanged}
107
+ openMenu={temporaryOpenMenu}
108
+ >
109
+ <Toolbar id={'navigation-header'}>
110
+ {
111
+ !isXSmall &&
112
+ <Item
113
+ location={'before'}
114
+ cssClass={'menu-button'}
115
+ >
116
+ <Button icon="menu" stylingMode="text" onClick={toggleMenu} />
117
+ </Item>
118
+ }
119
+ <Item location={'before'} cssClass={'header-title'} text={title} />
120
+ </Toolbar>
121
+ </SideNavigationMenu>
122
+ </Template>
123
+ </Drawer>
124
+ </div>
125
+ );
126
+ }
127
+
128
+ const MenuStatus = {
129
+ Closed: 1,
130
+ Opened: 2,
131
+ TemporaryOpened: 3
132
+ };
133
+
@@ -0,0 +1,10 @@
1
+ .side-nav-outer-toolbar {
2
+ flex-direction: column;
3
+ display: flex;
4
+ height: 100%;
5
+ width: 100%;
6
+
7
+ .header-component {
8
+ z-index: 1505;
9
+ }
10
+ }