devextreme-cli 1.11.0-alpha.0 → 1.11.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 (100) hide show
  1. package/package.json +10 -9
  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 +16 -6
  6. package/src/applications/application.vue.js +6 -11
  7. package/src/templates/nextjs/application/.env +1 -0
  8. package/src/templates/nextjs/application/devextreme.json +63 -0
  9. package/src/templates/nextjs/application/next.config.mjs +32 -0
  10. package/src/templates/nextjs/application/public/logo192.png +0 -0
  11. package/src/templates/nextjs/application/public/logo512.png +0 -0
  12. package/src/templates/nextjs/application/public/manifest.json +25 -0
  13. package/src/templates/nextjs/application/public/robots.txt +3 -0
  14. package/src/templates/nextjs/application/src/app/actions/auth.ts +76 -0
  15. package/src/templates/nextjs/application/src/app/auth/[type]/page.tsx +49 -0
  16. package/src/templates/nextjs/application/src/app/layout.tsx +17 -0
  17. package/src/templates/nextjs/application/src/app/lib/session.ts +47 -0
  18. package/src/templates/nextjs/application/src/app/pages/layout.tsx +18 -0
  19. package/src/templates/nextjs/application/src/app-info.tsx +5 -0
  20. package/src/templates/nextjs/application/src/app-navigation.tsx +21 -0
  21. package/src/templates/nextjs/application/src/components/change-password-form/ChangePasswordForm.tsx +86 -0
  22. package/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.scss +19 -0
  23. package/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.tsx +107 -0
  24. package/src/templates/nextjs/application/src/components/footer/Footer.scss +12 -0
  25. package/src/templates/nextjs/application/src/components/footer/Footer.tsx +5 -0
  26. package/src/templates/nextjs/application/src/components/header/Header.scss +40 -0
  27. package/src/templates/nextjs/application/src/components/header/Header.tsx +38 -0
  28. package/src/templates/nextjs/application/src/components/index.tsx +7 -0
  29. package/src/templates/nextjs/application/src/components/login-form/LoginForm.scss +12 -0
  30. package/src/templates/nextjs/application/src/components/login-form/LoginForm.tsx +101 -0
  31. package/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.scss +12 -0
  32. package/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.tsx +78 -0
  33. package/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.scss +71 -0
  34. package/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.tsx +88 -0
  35. package/src/templates/nextjs/application/src/components/theme-switcher/ThemeSwitcher.tsx +21 -0
  36. package/src/templates/nextjs/application/src/components/user-panel/UserPanel.scss +51 -0
  37. package/src/templates/nextjs/application/src/components/user-panel/UserPanel.tsx +55 -0
  38. package/src/templates/nextjs/application/src/dx-styles.scss +106 -0
  39. package/src/templates/nextjs/application/src/index.css +12 -0
  40. package/src/templates/nextjs/application/src/layouts/index.tsx +3 -0
  41. package/src/templates/nextjs/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.scss +17 -0
  42. package/src/templates/nextjs/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx +133 -0
  43. package/src/templates/nextjs/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.scss +10 -0
  44. package/src/templates/nextjs/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx +119 -0
  45. package/src/templates/nextjs/application/src/layouts/single-card/single-card.scss +42 -0
  46. package/src/templates/nextjs/application/src/layouts/single-card/single-card.tsx +16 -0
  47. package/src/templates/nextjs/application/src/middleware.ts +46 -0
  48. package/src/templates/nextjs/application/src/theme.tsx +66 -0
  49. package/src/templates/nextjs/application/src/themes/metadata.additional.dark.json +11 -0
  50. package/src/templates/nextjs/application/src/themes/metadata.additional.json +11 -0
  51. package/src/templates/nextjs/application/src/themes/metadata.base.dark.json +8 -0
  52. package/src/templates/nextjs/application/src/themes/metadata.base.json +7 -0
  53. package/src/templates/nextjs/application/src/types.tsx +60 -0
  54. package/src/templates/nextjs/application/src/utils/default-user.tsx +7 -0
  55. package/src/templates/nextjs/application/src/utils/media-query.tsx +56 -0
  56. package/src/templates/nextjs/application/src/variables.scss +53 -0
  57. package/src/templates/nextjs/page/page.scss +0 -0
  58. package/src/templates/nextjs/page/page.tsx +13 -0
  59. package/src/templates/nextjs/sample-pages/home/home.scss +37 -0
  60. package/src/templates/nextjs/sample-pages/home/page.tsx +101 -0
  61. package/src/templates/nextjs/sample-pages/profile/page.tsx +61 -0
  62. package/src/templates/nextjs/sample-pages/profile/profile.scss +19 -0
  63. package/src/templates/nextjs/sample-pages/tasks/page.tsx +112 -0
  64. package/src/templates/nextjs/sample-pages/tasks/tasks.scss +3 -0
  65. package/src/templates/react/application/src/App.tsx +2 -1
  66. package/src/templates/react/application/src/Content.tsx +1 -1
  67. package/src/templates/react/application/src/app-routes.tsx +3 -3
  68. package/src/templates/react/application/src/components/change-password-form/ChangePasswordForm.tsx +1 -1
  69. package/src/templates/react/application/src/components/create-account-form/CreateAccountForm.tsx +1 -1
  70. package/src/templates/react/application/src/components/header/Header.scss +1 -1
  71. package/src/templates/react/application/src/components/login-form/LoginForm.tsx +1 -1
  72. package/src/templates/react/application/src/components/side-navigation-menu/SideNavigationMenu.tsx +2 -2
  73. package/src/templates/react/application/src/components/user-panel/UserPanel.tsx +1 -1
  74. package/src/templates/react/application/src/contexts/auth-hooks.ts +8 -0
  75. package/src/templates/react/application/src/contexts/auth.tsx +7 -5
  76. package/src/templates/react/application/src/contexts/navigation-hooks.ts +22 -0
  77. package/src/templates/react/application/src/contexts/navigation.tsx +3 -17
  78. package/src/templates/react/application/src/dx-styles.scss +3 -3
  79. package/src/templates/react/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx +3 -3
  80. package/src/templates/react/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx +3 -3
  81. package/src/templates/react/application/src/types.tsx +2 -2
  82. package/src/templates/react/page/page.tsx +1 -1
  83. package/src/templates/react/sample-pages/home/home.tsx +1 -1
  84. package/src/templates/react/sample-pages/index.tsx +3 -3
  85. package/src/templates/react/sample-pages/profile/profile.tsx +1 -1
  86. package/src/templates/react/sample-pages/tasks/tasks.tsx +1 -1
  87. package/src/templates/vue-v3/application/eslint.config.js +32 -0
  88. package/src/templates/vue-v3/application/src/App.vue +1 -1
  89. package/src/templates/vue-v3/application/src/components/header-toolbar.vue +2 -2
  90. package/src/templates/vue-v3/application/src/dx-styles.scss +4 -3
  91. package/src/templates/vue-v3/application/src/layouts/side-nav-inner-toolbar.vue +2 -2
  92. package/src/templates/vue-v3/application/src/layouts/side-nav-outer-toolbar.vue +2 -2
  93. package/src/templates/vue-v3/application/src/main.js +1 -1
  94. package/src/templates/vue-v3/application/src/router.js +5 -5
  95. package/src/utility/latest-versions.js +7 -4
  96. package/src/utility/module.js +13 -5
  97. package/src/utility/prompts/react-app-type.js +17 -0
  98. package/src/utility/run-command.js +10 -2
  99. package/src/utility/template-creator.js +8 -4
  100. package/src/templates/vue-v3/application/vue.config.js +0 -1
@@ -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
+ }
@@ -0,0 +1,119 @@
1
+ 'use client'
2
+ import Drawer from 'devextreme-react/drawer';
3
+ import { ScrollView<%=#isTypeScript%>, ScrollViewRef<%=/isTypeScript%> } from 'devextreme-react/scroll-view';
4
+ import React, { useState, useCallback, useRef } from 'react';
5
+ import { useRouter } from 'next/navigation';
6
+ import { Header, SideNavigationMenu } from '@/components';
7
+ import './side-nav-outer-toolbar.scss';
8
+ import { useScreenSize } from '@/utils/media-query';
9
+ import { Template } from 'devextreme-react/core/template';
10
+ <%=#isTypeScript%>import type { ButtonTypes } from 'devextreme-react/button';
11
+ import type { TreeViewTypes } from 'devextreme-react/tree-view';
12
+ import type { SideNavToolbarProps } from '@/types';
13
+ <%=/isTypeScript%>
14
+ export default function SideNavOuterToolbar({ title, children }<%=#isTypeScript%>: React.PropsWithChildren<SideNavToolbarProps><%=/isTypeScript%>) {
15
+ const scrollViewRef = useRef<%=#isTypeScript%><ScrollViewRef><%=/isTypeScript%>(null);
16
+ const router = useRouter();
17
+ const { isXSmall, isLarge } = useScreenSize();
18
+ const [menuStatus, setMenuStatus] = useState(
19
+ isLarge ? MenuStatus.Opened : MenuStatus.Closed
20
+ );
21
+
22
+ const toggleMenu = useCallback(({ event }<%=#isTypeScript%>: ButtonTypes.ClickEvent<%=/isTypeScript%>) => {
23
+ setMenuStatus(
24
+ prevMenuStatus => prevMenuStatus === MenuStatus.Closed
25
+ ? MenuStatus.Opened
26
+ : MenuStatus.Closed
27
+ );
28
+ event<%=#isTypeScript%>?<%=/isTypeScript%>.stopPropagation();
29
+ }, []);
30
+
31
+ const temporaryOpenMenu = useCallback(() => {
32
+ setMenuStatus(
33
+ prevMenuStatus => prevMenuStatus === MenuStatus.Closed
34
+ ? MenuStatus.TemporaryOpened
35
+ : prevMenuStatus
36
+ );
37
+ }, []);
38
+
39
+ const onOutsideClick = useCallback(() => {
40
+ setMenuStatus(
41
+ prevMenuStatus => prevMenuStatus !== MenuStatus.Closed && !isLarge
42
+ ? MenuStatus.Closed
43
+ : prevMenuStatus
44
+ );
45
+ return menuStatus === MenuStatus.Closed ? true : false;
46
+ }, [isLarge, menuStatus]);
47
+
48
+ const onNavigationChanged = useCallback(({ itemData, event, node }<%=#isTypeScript%>: TreeViewTypes.ItemClickEvent<%=/isTypeScript%>) => {
49
+ if (menuStatus === MenuStatus.Closed || !itemData<%=#isTypeScript%>?<%=/isTypeScript%>.path || node<%=#isTypeScript%>?<%=/isTypeScript%>.selected) {
50
+ event<%=#isTypeScript%>?<%=/isTypeScript%>.preventDefault();
51
+ return;
52
+ }
53
+
54
+ router.push(itemData.path);
55
+ scrollViewRef.current<%=#isTypeScript%>?<%=/isTypeScript%>.instance().scrollTo(0);
56
+
57
+ if (!isLarge || menuStatus === MenuStatus.TemporaryOpened) {
58
+ setMenuStatus(MenuStatus.Closed);
59
+ event<%=#isTypeScript%>?<%=/isTypeScript%>.stopPropagation();
60
+ }
61
+ }, [router, menuStatus, isLarge]);
62
+
63
+ return (
64
+ <div className={'side-nav-outer-toolbar'}>
65
+ <Header
66
+ menuToggleEnabled
67
+ toggleMenu={toggleMenu}
68
+ title={title}
69
+ />
70
+ <Drawer
71
+ className={'drawer layout-body'}
72
+ position={'before'}
73
+ closeOnOutsideClick={onOutsideClick}
74
+ openedStateMode={isLarge ? 'shrink' : 'overlap'}
75
+ revealMode={isXSmall ? 'slide' : 'expand'}
76
+ minSize={isXSmall ? 0 : 60}
77
+ maxSize={250}
78
+ shading={isLarge ? false : true}
79
+ opened={menuStatus === MenuStatus.Closed ? false : true}
80
+ template={'menu'}
81
+ >
82
+ <div className={'container'}>
83
+ <ScrollView ref={scrollViewRef} className={'with-footer'}>
84
+ <div className={'content'}>
85
+ {React.Children.map(children, (item) => {
86
+ if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type !== "footer") {
87
+ return item;
88
+ }
89
+ return null;
90
+ })}
91
+ </div>
92
+ <div className={'content-block'}>
93
+ {React.Children.map(children, (item) => {
94
+ if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type === "footer") {
95
+ return item;
96
+ }
97
+ return null;
98
+ })}
99
+ </div>
100
+ </ScrollView>
101
+ </div>
102
+ <Template name={'menu'}>
103
+ <SideNavigationMenu
104
+ compactMode={menuStatus === MenuStatus.Closed}
105
+ selectedItemChanged={onNavigationChanged}
106
+ openMenu={temporaryOpenMenu}
107
+ >
108
+ </SideNavigationMenu>
109
+ </Template>
110
+ </Drawer>
111
+ </div>
112
+ );
113
+ }
114
+
115
+ const MenuStatus = {
116
+ Closed: 1,
117
+ Opened: 2,
118
+ TemporaryOpened: 3
119
+ };
@@ -0,0 +1,42 @@
1
+ .single-card {
2
+ width: 100%;
3
+ height: 100%;
4
+
5
+ .dx-card {
6
+ width: 360px;
7
+ margin: auto auto;
8
+ padding: 24px;
9
+ flex-grow: 0;
10
+ border-radius: 8px;
11
+
12
+ @media (max-width: 599.99px) {
13
+ width: 100%;
14
+ height: 100%;
15
+ border-radius: 0;
16
+ box-shadow: none;
17
+ margin: 0;
18
+ border: 0;
19
+ flex-grow: 1;
20
+ }
21
+
22
+ .header {
23
+ margin: 24px 0;
24
+
25
+ .title {
26
+ color: var(--base-text-color);
27
+ font-weight: 500;
28
+ font-size: 24px;
29
+ text-align: center;
30
+ line-height: 24px;
31
+ }
32
+
33
+ .description {
34
+ color: var(--base-text-color-alpha-7);
35
+ line-height: 16px;
36
+ font-size: 12px;
37
+ margin-top: 32px;
38
+ text-align: center;
39
+ }
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,16 @@
1
+ import ScrollView from 'devextreme-react/scroll-view';
2
+ import './single-card.scss';
3
+ <%=#isTypeScript%>import type { SingleCardProps } from '@/types';<%=/isTypeScript%>
4
+
5
+ export default function SingleCard({ title, description, children }<%=#isTypeScript%>: React.PropsWithChildren<SingleCardProps><%=/isTypeScript%>) {
6
+ return (
7
+ <ScrollView height={'100%'} width={'100%'} className={'with-footer single-card'}>
8
+ <div className={'dx-card content'}>
9
+ <div className={'header'}>
10
+ <div className={'title'}>{title}</div>
11
+ <div className={'description'}>{description}</div>
12
+ </div>
13
+ {children}
14
+ </div>
15
+ </ScrollView>
16
+ )}
@@ -0,0 +1,46 @@
1
+ import { NextResponse<%=#isTypeScript%>, type NextRequest<%=/isTypeScript%> } from 'next/server';
2
+ import { cookies } from 'next/headers';
3
+ import { decrypt, createSession } from '@/app/lib/session';
4
+ import defaultUser from '@/utils/default-user';
5
+
6
+ const isProtectedRoute = (path<%=#isTypeScript%>: string<%=/isTypeScript%>) => path.startsWith('/pages');
7
+
8
+ async function _DEMO_logIn() {
9
+ await createSession(defaultUser.id);
10
+
11
+ return NextResponse.next();
12
+ }
13
+
14
+ <%=#isTypeScript%>// eslint-disable-next-line @typescript-eslint/no-unused-vars
15
+ <%=/isTypeScript%>async function redirectUnauthorized(req<%=#isTypeScript%>: NextRequest<%=/isTypeScript%>) {
16
+ return await _DEMO_logIn();
17
+
18
+ // In production, you will need to redirect unauthorized users
19
+ // return NextResponse.redirect(new URL('/auth/login', req.nextUrl))
20
+ }
21
+
22
+ export default async function middleware(req<%=#isTypeScript%>: NextRequest<%=/isTypeScript%>) {
23
+ const path = req.nextUrl.pathname;
24
+
25
+ if (!isProtectedRoute(path)) {
26
+ return NextResponse.next();
27
+ }
28
+
29
+ const cookie = (await cookies()).get('session')?.value;
30
+
31
+ if (!cookie) {
32
+ return await redirectUnauthorized(req);
33
+ }
34
+
35
+ const session = await decrypt(cookie);
36
+
37
+ if (!session?.userId) {
38
+ return await redirectUnauthorized(req);
39
+ }
40
+
41
+ return NextResponse.next();
42
+ }
43
+
44
+ export const config = {
45
+ matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
46
+ }
@@ -0,0 +1,66 @@
1
+ 'use client'
2
+ import React, { useCallback, useMemo, useState, useLayoutEffect } from 'react';
3
+
4
+ const themes = ['light', 'dark'];
5
+ const themeClassNamePrefix = 'dx-swatch-';
6
+ let currentTheme = getNextTheme();
7
+
8
+ function getNextTheme(theme = '') {
9
+ return themes[themes.indexOf(theme) + 1] || themes[0];
10
+ }
11
+
12
+ function getCurrentTheme() {
13
+ return currentTheme;
14
+ }
15
+
16
+ function toggleTheme(prevTheme<%=#isTypeScript%>: string<%=/isTypeScript%>) {
17
+ const isCurrentThemeDark = prevTheme === 'dark';
18
+ const newTheme = getNextTheme(prevTheme);
19
+
20
+ if (typeof window !== 'undefined') {
21
+ document.body.classList.replace(
22
+ themeClassNamePrefix + prevTheme,
23
+ themeClassNamePrefix + newTheme
24
+ );
25
+
26
+ const additionalClassNamePrefix = themeClassNamePrefix + 'additional';
27
+ const additionalClassNamePostfix = isCurrentThemeDark ? '-' + prevTheme : '';
28
+ const additionalClassName = `${additionalClassNamePrefix}${additionalClassNamePostfix}`
29
+
30
+ document.body
31
+ .querySelector(`.${additionalClassName}`)?.classList
32
+ .replace(additionalClassName, additionalClassNamePrefix + (isCurrentThemeDark ? '' : '-dark'));
33
+
34
+ currentTheme = newTheme;
35
+ }
36
+
37
+ return newTheme;
38
+ }
39
+
40
+ export function useThemeContext() {
41
+ const [theme, setTheme] = useState(getCurrentTheme());
42
+ const switchTheme = useCallback(() => setTheme((currentTheme) => toggleTheme(currentTheme)), []);
43
+ const isDark = useCallback(()<%=#isTypeScript%>: boolean<%=/isTypeScript%> => {
44
+ return currentTheme === 'dark';
45
+ }, []);
46
+
47
+ useLayoutEffect(() => {
48
+ if (typeof window !== 'undefined' && !document.body.className.includes(themeClassNamePrefix)) {
49
+ document.body.classList.add(themeClassNamePrefix + theme);
50
+ }
51
+ }, [theme]);
52
+
53
+ return useMemo(()=> ({ theme, switchTheme, isDark }), [theme, switchTheme, isDark]);
54
+ }
55
+
56
+ export const ThemeContext = React.createContext<%=#isTypeScript%><ReturnType<typeof useThemeContext> | null><%=/isTypeScript%>(null);
57
+
58
+ export const ThemeProvider = ({ children }<%=#isTypeScript%>: React.PropsWithChildren<%=/isTypeScript%>) => {
59
+ const themeContext = useThemeContext();
60
+
61
+ return (
62
+ <ThemeContext.Provider value={themeContext}>
63
+ { children }
64
+ </ThemeContext.Provider>
65
+ );
66
+ };
@@ -0,0 +1,11 @@
1
+ {
2
+ "items": [],
3
+ "baseTheme": "fluent.blue.dark",
4
+ "assetsBasePath": "../../../node_modules/devextreme/dist/css/",
5
+ "outputColorScheme": "additional-dark",
6
+ "makeSwatch": true,
7
+ "base": true,
8
+ "widgets": [
9
+ "treeview"
10
+ ]
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "items": [],
3
+ "baseTheme": "fluent.blue.light",
4
+ "assetsBasePath": "../../../node_modules/devextreme/dist/css/",
5
+ "outputColorScheme": "additional",
6
+ "makeSwatch": true,
7
+ "base": true,
8
+ "widgets": [
9
+ "treeview"
10
+ ]
11
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "items": [],
3
+ "baseTheme": "fluent.blue.dark",
4
+ "assetsBasePath": "../../../node_modules/devextreme/dist/css/",
5
+ "outputColorScheme": "dark",
6
+ "base": true,
7
+ "makeSwatch": true
8
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "items": [],
3
+ "baseTheme": "fluent.blue.light",
4
+ "assetsBasePath": "../../../node_modules/devextreme/dist/css/",
5
+ "outputColorScheme": "base",
6
+ "base": true
7
+ }
@@ -0,0 +1,60 @@
1
+ import { TreeViewTypes } from 'devextreme-react/tree-view';
2
+ import { ButtonTypes } from 'devextreme-react/button';
3
+
4
+ export interface HeaderProps {
5
+ menuToggleEnabled: boolean;
6
+ title?: string;
7
+ toggleMenu: (e: ButtonTypes.ClickEvent) => void;
8
+ }
9
+
10
+ export interface SideNavigationMenuProps {
11
+ selectedItemChanged: (e: TreeViewTypes.ItemClickEvent) => void;
12
+ openMenu: (e: React.PointerEvent) => void;
13
+ compactMode: boolean;
14
+ onMenuReady?: (e: TreeViewTypes.ContentReadyEvent) => void;
15
+ }
16
+
17
+ export interface UserPanelProps {
18
+ menuMode: 'context' | 'list';
19
+ }
20
+
21
+ export type SessionPayload = {
22
+ userId: string;
23
+ expiresAt: Date;
24
+ }
25
+
26
+ export interface User {
27
+ email: string;
28
+ avatarUrl: string;
29
+ }
30
+
31
+ export type AuthContextType = {
32
+ user?: User;
33
+ signIn: (email: string, password: string) => Promise<{isOk: boolean, data?: User, message?: string}>;
34
+ signOut: () => void;
35
+ loading: boolean;
36
+ }
37
+
38
+ export interface SideNavToolbarProps {
39
+ title: string;
40
+ }
41
+
42
+ export interface SingleCardProps {
43
+ title?: string;
44
+ description?: string;
45
+ }
46
+
47
+ export type Handle = () => void;
48
+
49
+ interface NavigationData {
50
+ currentPath: string;
51
+ }
52
+
53
+ export type NavigationContextType = {
54
+ setNavigationData?: ({ currentPath }: NavigationData) => void;
55
+ navigationData: NavigationData;
56
+ }
57
+
58
+ export type ValidationType = {
59
+ value: string;
60
+ }
@@ -0,0 +1,7 @@
1
+ const userInfo = {
2
+ id: '1',
3
+ email: 'sandra@example.com',
4
+ avatarUrl: 'https://js.devexpress.com/Demos/WidgetsGallery/JSDemos/images/employees/06.png'
5
+ }
6
+
7
+ export default userInfo;
@@ -0,0 +1,56 @@
1
+ 'use client'
2
+ import { useState, useCallback, useEffect } from 'react';
3
+ <%=#isTypeScript%>import type { Handle } from '@/types';
4
+ <%=/isTypeScript%>
5
+ export const useScreenSize = () => {
6
+ const [screenSize, setScreenSize] = useState(getScreenSize());
7
+ const onSizeChanged = useCallback(() => {
8
+ setScreenSize(getScreenSize());
9
+ }, []);
10
+
11
+ useEffect(() => {
12
+ subscribe(onSizeChanged);
13
+
14
+ return () => {
15
+ unsubscribe(onSizeChanged);
16
+ };
17
+ }, [onSizeChanged]);
18
+
19
+ return screenSize;
20
+ };
21
+
22
+ let handlers<%=#isTypeScript%>: Handle[]<%=/isTypeScript%> = [];
23
+ let xSmallMedia<%=#isTypeScript%>: MediaQueryList<%=/isTypeScript%>;
24
+ let smallMedia<%=#isTypeScript%>: MediaQueryList<%=/isTypeScript%>;
25
+ let mediumMedia<%=#isTypeScript%>: MediaQueryList<%=/isTypeScript%>;
26
+ let largeMedia<%=#isTypeScript%>: MediaQueryList<%=/isTypeScript%>;
27
+
28
+ if (typeof window !== 'undefined') {
29
+ xSmallMedia = window.matchMedia('(max-width: 599.99px)');
30
+ smallMedia = window.matchMedia('(min-width: 600px) and (max-width: 959.99px)');
31
+ mediumMedia = window.matchMedia('(min-width: 960px) and (max-width: 1279.99px)');
32
+ largeMedia = window.matchMedia('(min-width: 1280px)');
33
+
34
+ [xSmallMedia, smallMedia, mediumMedia, largeMedia].forEach(media => {
35
+ media.addListener((e) => {
36
+ if(e.matches) {
37
+ handlers.forEach(handler => handler())
38
+ }
39
+ });
40
+ });
41
+ }
42
+
43
+ const subscribe = (handler<%=#isTypeScript%>: Handle<%=/isTypeScript%>) => handlers.push(handler);
44
+
45
+ const unsubscribe = (handler<%=#isTypeScript%>: Handle<%=/isTypeScript%>) => {
46
+ handlers = handlers.filter(item => item !== handler);
47
+ };
48
+
49
+ function getScreenSize() {
50
+ return {
51
+ isXSmall: xSmallMedia?.matches,
52
+ isSmall: smallMedia?.matches,
53
+ isMedium: mediumMedia?.matches,
54
+ isLarge: largeMedia?.matches
55
+ };
56
+ }
@@ -0,0 +1,53 @@
1
+ @use 'sass:meta';
2
+ @use 'sass:color';
3
+ @use 'sass:map';
4
+ @use "./themes/generated/variables.base.scss" as variablesBase;
5
+ @use "./themes/generated/variables.base.dark.scss" as variablesBaseDark;
6
+ @use "./themes/generated/variables.additional.scss" as variablesAdditional;
7
+ @use "./themes/generated/variables.additional.dark.scss" as variablesAdditionalDark;
8
+
9
+ @mixin theme-variables($theme-name) {
10
+ $theme: meta.module-variables($theme-name);
11
+ $base-text-color: map.get($theme, 'base-text-color');
12
+ $base-bg: map.get($theme, 'base-bg');
13
+
14
+ --base-text-color: #{$base-text-color};
15
+ --base-bg: #{$base-bg};
16
+ --base-bg-darken-5: #{color.adjust($base-bg, $lightness: -5%)};
17
+ --base-accent: #{map.get($theme, 'base-accent')};
18
+ --base-text-color-alpha-7: #{rgba($base-text-color, color.alpha($base-text-color) * 0.7)};
19
+ }
20
+
21
+ :root {
22
+ body {
23
+ @include theme-variables('variablesBase');
24
+
25
+ --footer-border-color: rgba(224, 224, 224, 1);
26
+ --plus-icon-color: #242424;
27
+ --devextreme-logo-color: #596C7D;
28
+ --vue-logo-text-color: #35495E;
29
+
30
+ --shadow-color-first: rgba(0, 0, 0, 0.06);
31
+ --shadow-color-second: rgba(0, 0, 0, 0.12);
32
+ }
33
+
34
+ .dx-swatch-additional {
35
+ @include theme-variables('variablesAdditional');
36
+ }
37
+
38
+ .dx-swatch-dark {
39
+ @include theme-variables('variablesBaseDark');;
40
+
41
+ --plus-icon-color: #fff;
42
+ --devextreme-logo-color: #fff;
43
+ --vue-logo-text-color: #fff;
44
+
45
+ --shadow-color-first: rgba(0, 0, 0, 0.12);
46
+ --shadow-color-second: rgba(0, 0, 0, 0.24);
47
+ --footer-border-color: rgba(97, 97, 97, 1);
48
+ }
49
+
50
+ .dx-swatch-additional-dark {
51
+ @include theme-variables('variablesAdditionalDark');
52
+ }
53
+ }
File without changes
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import './<%=pageName%>.scss';
3
+
4
+ export default function Page() {
5
+ return <React.Fragment>
6
+ <h2><%=title%></h2>
7
+ <div className={'content-block'}>
8
+ <div className={'dx-card responsive-paddings'}>
9
+ Put your content here
10
+ </div>
11
+ </div>
12
+ </React.Fragment>
13
+ };