@dhis2-ui/header-bar 10.16.2 → 10.16.3-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/package.json +17 -16
  2. package/src/__e2e__/header-bar.e2e.stories.js +26 -0
  3. package/src/__e2e__/stories/common.js +226 -0
  4. package/src/__e2e__/stories/custom-application-title.js +19 -0
  5. package/src/__e2e__/stories/default.js +13 -0
  6. package/src/__e2e__/stories/me-with-avatar.js +27 -0
  7. package/src/__e2e__/stories/modulesWithSpecialCharacters.js +229 -0
  8. package/src/__e2e__/stories/online-status-message.js +49 -0
  9. package/src/__e2e__/stories/pwa-enabled.js +17 -0
  10. package/src/__e2e__/stories/user-has-all-authority.js +20 -0
  11. package/src/__e2e__/stories/user-has-no-authorities.js +20 -0
  12. package/src/__e2e__/stories/user-has-web-interpretation-and-messaging-authority.js +22 -0
  13. package/src/__e2e__/stories/user-has-web-interpretation-authority.js +22 -0
  14. package/src/__e2e__/stories/user-has-web-messaging-authority.js +22 -0
  15. package/src/__e2e__/stories/with-debug-info-edge-cases.js +51 -0
  16. package/src/__e2e__/stories/with-special-app-name-character.js +23 -0
  17. package/src/__e2e__/stories/with-update-available-notification.js +39 -0
  18. package/src/__e2e__/stories/zero-unread-interpretations.js +19 -0
  19. package/src/__e2e__/stories/zero-unread-messages.js +19 -0
  20. package/src/apps.js +276 -0
  21. package/src/debug-info/debug-info-menu-item.js +72 -0
  22. package/src/debug-info/debug-info-modal.js +47 -0
  23. package/src/debug-info/debug-info-table.js +51 -0
  24. package/src/debug-info/use-debug-info.js +15 -0
  25. package/src/features/common/index.js +14 -0
  26. package/src/features/the_headerbar_can_display_online_status/the_headerbar_displays_online_status.js +158 -0
  27. package/src/features/the_headerbar_can_display_online_status.feature +57 -0
  28. package/src/features/the_headerbar_conditionally_renders_notification_icons/the_headerbar_conditionally_renders_notification_icons.js +43 -0
  29. package/src/features/the_headerbar_conditionally_renders_notification_icons.feature +26 -0
  30. package/src/features/the_headerbar_contains_a_menu_to_all_apps/common.js +5 -0
  31. package/src/features/the_headerbar_contains_a_menu_to_all_apps/the_app_menu_closes_when_the_user_clicks_outside.js +9 -0
  32. package/src/features/the_headerbar_contains_a_menu_to_all_apps/the_headerbar_contains_a_menu_icon.js +5 -0
  33. package/src/features/the_headerbar_contains_a_menu_to_all_apps/the_user_will_be_offered_a_menu_with_5_apps.js +16 -0
  34. package/src/features/the_headerbar_contains_a_menu_to_all_apps.feature +21 -0
  35. package/src/features/the_headerbar_contains_a_profile_menu/common.js +14 -0
  36. package/src/features/the_headerbar_contains_a_profile_menu/the_headerbar_shows_a_text_icon_if_the_user_does_not_have_an_avatar.js +30 -0
  37. package/src/features/the_headerbar_contains_a_profile_menu/the_headerbar_shows_an_image_icon_if_the_user_has_an_avatar.js +23 -0
  38. package/src/features/the_headerbar_contains_a_profile_menu/the_menu_is_closed_by_default.js +1 -0
  39. package/src/features/the_headerbar_contains_a_profile_menu/the_menu_opens.js +14 -0
  40. package/src/features/the_headerbar_contains_a_profile_menu/the_profile_menu_closes_when_the_user_clicks_outside.js +5 -0
  41. package/src/features/the_headerbar_contains_a_profile_menu/the_user_can_edit_his_profile.js +7 -0
  42. package/src/features/the_headerbar_contains_a_profile_menu/the_user_can_go_to_his_account.js +7 -0
  43. package/src/features/the_headerbar_contains_a_profile_menu/the_user_can_go_to_the_about_dhis2_page.js +7 -0
  44. package/src/features/the_headerbar_contains_a_profile_menu/the_user_can_go_to_the_help_page.js +7 -0
  45. package/src/features/the_headerbar_contains_a_profile_menu/the_user_can_go_to_the_settings.js +7 -0
  46. package/src/features/the_headerbar_contains_a_profile_menu/the_user_can_log_out.js +53 -0
  47. package/src/features/the_headerbar_contains_a_profile_menu/the_user_name_and_email_are_displayed.js +22 -0
  48. package/src/features/the_headerbar_contains_a_profile_menu.feature +73 -0
  49. package/src/features/the_headerbar_displays_a_link_to_interpretations_and_an_unread_count/the_headerbar_displays_a_link_to_the_interpretations.js +5 -0
  50. package/src/features/the_headerbar_displays_a_link_to_interpretations_and_an_unread_count/there_are_no_unread_interpretations.js +9 -0
  51. package/src/features/the_headerbar_displays_a_link_to_interpretations_and_an_unread_count/there_are_some_unread_interpretations.js +12 -0
  52. package/src/features/the_headerbar_displays_a_link_to_interpretations_and_an_unread_count.feature +13 -0
  53. package/src/features/the_headerbar_displays_a_link_to_messages_and_an_unread_count/the_headerbar_displays_a_link_to_the_messages.js +5 -0
  54. package/src/features/the_headerbar_displays_a_link_to_messages_and_an_unread_count/there_are_no_unread_messages.js +9 -0
  55. package/src/features/the_headerbar_displays_a_link_to_messages_and_an_unread_count/there_are_some_unread_messages.js +7 -0
  56. package/src/features/the_headerbar_displays_a_link_to_messages_and_an_unread_count.feature +13 -0
  57. package/src/features/the_headerbar_should_contain_a_logo_that_links_to_the_homepage/headerbar_contains_logo.js +12 -0
  58. package/src/features/the_headerbar_should_contain_a_logo_that_links_to_the_homepage.feature +6 -0
  59. package/src/features/the_headerbar_should_display_app_update_notification/index.js +52 -0
  60. package/src/features/the_headerbar_should_display_app_update_notification.feature +22 -0
  61. package/src/features/the_headerbar_should_display_debug_version_infos/index.js +130 -0
  62. package/src/features/the_headerbar_should_display_debug_version_infos.feature +52 -0
  63. package/src/features/the_headerbar_should_display_the_title_provided_by_the_backend_and_the_app/the_headerbar_displays_the_custom_title.js +11 -0
  64. package/src/features/the_headerbar_should_display_the_title_provided_by_the_backend_and_the_app.feature +5 -0
  65. package/src/features/the_search_should_escape_regexp_character/common.js +6 -0
  66. package/src/features/the_search_should_escape_regexp_character/the_modules_do_not_contain_items_with_special_chars.js +23 -0
  67. package/src/features/the_search_should_escape_regexp_character/the_user_searches_for_an_app_with_a_regex_character.js +29 -0
  68. package/src/features/the_search_should_escape_regexp_character.feature +48 -0
  69. package/src/header-bar-context.js +28 -0
  70. package/src/header-bar.js +145 -0
  71. package/src/header-bar.prod.stories.js +303 -0
  72. package/src/index.js +1 -0
  73. package/src/join-path.js +4 -0
  74. package/src/locales/ar/translations.json +24 -0
  75. package/src/locales/ar_IQ/translations.json +24 -0
  76. package/src/locales/bn/translations.json +12 -0
  77. package/src/locales/ckb/translations.json +23 -0
  78. package/src/locales/cs/translations.json +24 -0
  79. package/src/locales/da/translations.json +24 -0
  80. package/src/locales/en/translations.json +25 -0
  81. package/src/locales/en_US/translations.json +25 -0
  82. package/src/locales/es/translations.json +25 -0
  83. package/src/locales/es_419/translations.json +24 -0
  84. package/src/locales/fr/translations.json +25 -0
  85. package/src/locales/hi_IN/translations.json +25 -0
  86. package/src/locales/id/translations.json +24 -0
  87. package/src/locales/index.js +88 -0
  88. package/src/locales/km/translations.json +24 -0
  89. package/src/locales/lo/translations.json +24 -0
  90. package/src/locales/my/translations.json +24 -0
  91. package/src/locales/nb/translations.json +24 -0
  92. package/src/locales/nl/translations.json +24 -0
  93. package/src/locales/or/translations.json +12 -0
  94. package/src/locales/prs/translations.json +24 -0
  95. package/src/locales/ps/translations.json +24 -0
  96. package/src/locales/pt/translations.json +25 -0
  97. package/src/locales/pt_BR/translations.json +24 -0
  98. package/src/locales/ro/translations.json +23 -0
  99. package/src/locales/ru/translations.json +24 -0
  100. package/src/locales/si/translations.json +24 -0
  101. package/src/locales/sv/translations.json +24 -0
  102. package/src/locales/tet/translations.json +24 -0
  103. package/src/locales/tg/translations.json +24 -0
  104. package/src/locales/uk/translations.json +24 -0
  105. package/src/locales/ur/translations.json +24 -0
  106. package/src/locales/uz_Latn/translations.json +23 -0
  107. package/src/locales/uz_UZ_Cyrl/translations.json +24 -0
  108. package/src/locales/uz_UZ_Latn/translations.json +24 -0
  109. package/src/locales/vi/translations.json +24 -0
  110. package/src/locales/zh/translations.json +25 -0
  111. package/src/locales/zh_CN/translations.json +24 -0
  112. package/src/logo-image.js +71 -0
  113. package/src/logo.js +45 -0
  114. package/src/notification-icon.js +91 -0
  115. package/src/notifications.js +63 -0
  116. package/src/online-status.js +40 -0
  117. package/src/online-status.styles.js +91 -0
  118. package/src/profile/use-on-doc-click.js +23 -0
  119. package/src/profile/use-on-doc-click.test.js +40 -0
  120. package/src/profile-menu/index.js +1 -0
  121. package/src/profile-menu/profile-header.js +118 -0
  122. package/src/profile-menu/profile-menu.js +176 -0
  123. package/src/profile-menu/update-notification.js +67 -0
  124. package/src/profile.js +101 -0
  125. package/src/title.js +23 -0
@@ -0,0 +1,20 @@
1
+ import React from 'react'
2
+ import { HeaderBar } from '../../index.js'
3
+ import {
4
+ dataProviderData,
5
+ createDecoratorCustomDataProviderHeaderBar,
6
+ createDecoratorProvider,
7
+ } from './common.js'
8
+
9
+ export const UserHasNoAuthorities = () => <HeaderBar appName="Example!" />
10
+
11
+ UserHasNoAuthorities.decorators = [
12
+ createDecoratorCustomDataProviderHeaderBar({
13
+ ...dataProviderData,
14
+ me: {
15
+ ...dataProviderData.me,
16
+ authorities: [],
17
+ },
18
+ }),
19
+ createDecoratorProvider(),
20
+ ]
@@ -0,0 +1,22 @@
1
+ import React from 'react'
2
+ import { HeaderBar } from '../../index.js'
3
+ import {
4
+ dataProviderData,
5
+ createDecoratorCustomDataProviderHeaderBar,
6
+ createDecoratorProvider,
7
+ } from './common.js'
8
+
9
+ export const UserHasWebInterpretationAndMessagingAuthority = () => (
10
+ <HeaderBar appName="Example!" />
11
+ )
12
+
13
+ UserHasWebInterpretationAndMessagingAuthority.decorators = [
14
+ createDecoratorCustomDataProviderHeaderBar({
15
+ ...dataProviderData,
16
+ me: {
17
+ ...dataProviderData.me,
18
+ authorities: ['M_dhis-web-interpretation', 'M_dhis-web-messaging'],
19
+ },
20
+ }),
21
+ createDecoratorProvider(),
22
+ ]
@@ -0,0 +1,22 @@
1
+ import React from 'react'
2
+ import { HeaderBar } from '../../index.js'
3
+ import {
4
+ dataProviderData,
5
+ createDecoratorCustomDataProviderHeaderBar,
6
+ createDecoratorProvider,
7
+ } from './common.js'
8
+
9
+ export const UserHasWebInterpretationAuthority = () => (
10
+ <HeaderBar appName="Example!" />
11
+ )
12
+
13
+ UserHasWebInterpretationAuthority.decorators = [
14
+ createDecoratorCustomDataProviderHeaderBar({
15
+ ...dataProviderData,
16
+ me: {
17
+ ...dataProviderData.me,
18
+ authorities: ['M_dhis-web-interpretation'],
19
+ },
20
+ }),
21
+ createDecoratorProvider(),
22
+ ]
@@ -0,0 +1,22 @@
1
+ import React from 'react'
2
+ import { HeaderBar } from '../../index.js'
3
+ import {
4
+ dataProviderData,
5
+ createDecoratorCustomDataProviderHeaderBar,
6
+ createDecoratorProvider,
7
+ } from './common.js'
8
+
9
+ export const UserHasWebMessagingAuthority = () => (
10
+ <HeaderBar appName="Example!" />
11
+ )
12
+
13
+ UserHasWebMessagingAuthority.decorators = [
14
+ createDecoratorCustomDataProviderHeaderBar({
15
+ ...dataProviderData,
16
+ me: {
17
+ ...dataProviderData.me,
18
+ authorities: ['M_dhis-web-messaging'],
19
+ },
20
+ }),
21
+ createDecoratorProvider(),
22
+ ]
@@ -0,0 +1,51 @@
1
+ import React from 'react'
2
+ import { HeaderBar } from '../../header-bar.js'
3
+ import {
4
+ createDecoratorCustomDataProviderHeaderBar,
5
+ createDecoratorProvider,
6
+ providerConfig,
7
+ } from './common.js'
8
+
9
+ export const WithUnknownInstanceVersion = () => <HeaderBar />
10
+
11
+ WithUnknownInstanceVersion.decorators = [
12
+ createDecoratorCustomDataProviderHeaderBar(),
13
+ createDecoratorProvider({
14
+ ...providerConfig,
15
+ systemInfo: {
16
+ ...providerConfig.systemInfo,
17
+ version: undefined,
18
+ },
19
+ }),
20
+ ]
21
+
22
+ export const WithUnknownAppVersion = () => <HeaderBar />
23
+
24
+ WithUnknownAppVersion.decorators = [
25
+ createDecoratorCustomDataProviderHeaderBar(),
26
+ createDecoratorProvider({
27
+ ...providerConfig,
28
+ appVersion: undefined,
29
+ }),
30
+ ]
31
+
32
+ export const WithUnknownAppName = () => <HeaderBar />
33
+
34
+ WithUnknownAppName.decorators = [
35
+ createDecoratorCustomDataProviderHeaderBar(),
36
+ createDecoratorProvider({
37
+ ...providerConfig,
38
+ appName: undefined,
39
+ }),
40
+ ]
41
+
42
+ export const WithUnknownAppNameAndVersion = () => <HeaderBar />
43
+
44
+ WithUnknownAppNameAndVersion.decorators = [
45
+ createDecoratorCustomDataProviderHeaderBar(),
46
+ createDecoratorProvider({
47
+ ...providerConfig,
48
+ appName: undefined,
49
+ appVersion: undefined,
50
+ }),
51
+ ]
@@ -0,0 +1,23 @@
1
+ import React from 'react'
2
+ import { HeaderBar } from '../../index.js'
3
+ import {
4
+ modulesWithSpecialCharacters,
5
+ dataProviderData,
6
+ createDecoratorCustomDataProviderHeaderBar,
7
+ createDecoratorProvider,
8
+ } from './common.js'
9
+
10
+ export const WithSpecialAppNameCharacters = () => (
11
+ <HeaderBar appName="Example!" />
12
+ )
13
+
14
+ WithSpecialAppNameCharacters.decorators = [
15
+ createDecoratorCustomDataProviderHeaderBar({
16
+ ...dataProviderData,
17
+ ['action::menu/getModules']: {
18
+ ...dataProviderData['action::menu/getModules'],
19
+ modules: modulesWithSpecialCharacters,
20
+ },
21
+ }),
22
+ createDecoratorProvider(),
23
+ ]
@@ -0,0 +1,39 @@
1
+ import React, { useState } from 'react'
2
+ import { HeaderBar } from '../../header-bar.js'
3
+ import {
4
+ createDecoratorCustomDataProviderHeaderBar,
5
+ createDecoratorProvider,
6
+ providerConfig,
7
+ } from './common.js'
8
+
9
+ export const WithUpdateAvailableNotification = () => {
10
+ const [modalOpen, setModalOpen] = useState(false)
11
+ return (
12
+ <>
13
+ <HeaderBar
14
+ updateAvailable={true}
15
+ onApplyAvailableUpdate={() => setModalOpen(true)}
16
+ />
17
+ {modalOpen && <div>The callback was successful</div>}
18
+ </>
19
+ )
20
+ }
21
+ WithUpdateAvailableNotification.decorators = [
22
+ createDecoratorCustomDataProviderHeaderBar(),
23
+ createDecoratorProvider({
24
+ ...providerConfig,
25
+ appName: 'Data Visualizer',
26
+ }),
27
+ ]
28
+
29
+ export const WithUpdateAvailableNotificationNoAppName = () => (
30
+ <HeaderBar updateAvailable={true} />
31
+ )
32
+
33
+ WithUpdateAvailableNotificationNoAppName.decorators = [
34
+ createDecoratorCustomDataProviderHeaderBar(),
35
+ createDecoratorProvider({
36
+ ...providerConfig,
37
+ appName: undefined,
38
+ }),
39
+ ]
@@ -0,0 +1,19 @@
1
+ import React from 'react'
2
+ import { HeaderBar } from '../../index.js'
3
+ import {
4
+ dataProviderData,
5
+ createDecoratorCustomDataProviderHeaderBar,
6
+ createDecoratorProvider,
7
+ } from './common.js'
8
+
9
+ export const ZeroUnreadInterpretations = () => <HeaderBar appName="Example!" />
10
+
11
+ ZeroUnreadInterpretations.decorators = [
12
+ createDecoratorCustomDataProviderHeaderBar({
13
+ ...dataProviderData,
14
+ ['me/dashboard']: {
15
+ unreadInterpretations: 0,
16
+ },
17
+ }),
18
+ createDecoratorProvider(),
19
+ ]
@@ -0,0 +1,19 @@
1
+ import React from 'react'
2
+ import { HeaderBar } from '../../index.js'
3
+ import {
4
+ dataProviderData,
5
+ createDecoratorCustomDataProviderHeaderBar,
6
+ createDecoratorProvider,
7
+ } from './common.js'
8
+
9
+ export const ZeroUnreadMessages = () => <HeaderBar appName="Example!" />
10
+
11
+ ZeroUnreadMessages.decorators = [
12
+ createDecoratorCustomDataProviderHeaderBar({
13
+ ...dataProviderData,
14
+ ['me/dashboard']: {
15
+ unreadMessages: 0,
16
+ },
17
+ }),
18
+ createDecoratorProvider(),
19
+ ]
package/src/apps.js ADDED
@@ -0,0 +1,276 @@
1
+ import { useConfig } from '@dhis2/app-runtime'
2
+ import { colors, spacers, theme } from '@dhis2/ui-constants'
3
+ import { IconApps24, IconSettings24 } from '@dhis2/ui-icons'
4
+ import { Card } from '@dhis2-ui/card'
5
+ import { InputField } from '@dhis2-ui/input'
6
+ import PropTypes from 'prop-types'
7
+ import React, { useState, useEffect, useCallback, useRef } from 'react'
8
+ import { joinPath } from './join-path.js'
9
+ import i18n from './locales/index.js'
10
+
11
+ /**
12
+ * Copied from here:
13
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
14
+ */
15
+ function escapeRegExpCharacters(text) {
16
+ return text.replace(/[/.*+?^${}()|[\]\\]/g, '\\$&')
17
+ }
18
+
19
+ function Search({ value, onChange }) {
20
+ const { baseUrl } = useConfig()
21
+
22
+ return (
23
+ <div>
24
+ <span>
25
+ <InputField
26
+ value={value}
27
+ name="filter"
28
+ placeholder={i18n.t('Search apps')}
29
+ onChange={onChange}
30
+ initialFocus
31
+ />
32
+ </span>
33
+
34
+ <span>
35
+ <a href={joinPath(baseUrl, 'dhis-web-menu-management')}>
36
+ <IconSettings24 color={colors.grey700} />
37
+ </a>
38
+ </span>
39
+
40
+ <style jsx>{`
41
+ div {
42
+ display: flex;
43
+ flex-direction: row;
44
+ flex-wrap: nowrap;
45
+ height: 52px;
46
+ margin: 8px;
47
+ }
48
+
49
+ span {
50
+ flex: 1 100%;
51
+ }
52
+
53
+ span:last-child {
54
+ flex: 1 auto;
55
+ margin: 8px;
56
+ }
57
+ `}</style>
58
+ </div>
59
+ )
60
+ }
61
+
62
+ Search.propTypes = {
63
+ value: PropTypes.string.isRequired,
64
+ onChange: PropTypes.func.isRequired,
65
+ }
66
+
67
+ function Item({ name, path, img }) {
68
+ return (
69
+ <a href={path}>
70
+ <img src={img} alt="app logo" />
71
+
72
+ <div>{name}</div>
73
+
74
+ <style jsx>{`
75
+ a {
76
+ display: inline-block;
77
+ display: flex;
78
+ flex-direction: column;
79
+ align-items: center;
80
+ justify-content: center;
81
+ width: 96px;
82
+ margin: 8px;
83
+ border-radius: 12px;
84
+ text-decoration: none;
85
+ cursor: pointer;
86
+ }
87
+
88
+ a:hover,
89
+ a:focus {
90
+ background-color: ${theme.primary050};
91
+ cursor: pointer;
92
+ }
93
+
94
+ a:hover > div {
95
+ font-weight: 500;
96
+ cursor: pointer;
97
+ }
98
+
99
+ img {
100
+ width: 48px;
101
+ height: 48px;
102
+ margin: 8px;
103
+ cursor: pointer;
104
+ }
105
+
106
+ div {
107
+ overflow-wrap: anywhere;
108
+ margin-top: 14px;
109
+ color: rgba(0, 0, 0, 0.87);
110
+ font-size: 12px;
111
+ letter-spacing: 0.01em;
112
+ line-height: 14px;
113
+ text-align: center;
114
+ cursor: pointer;
115
+ }
116
+ `}</style>
117
+ </a>
118
+ )
119
+ }
120
+
121
+ Item.propTypes = {
122
+ img: PropTypes.string,
123
+ name: PropTypes.string,
124
+ path: PropTypes.string,
125
+ }
126
+
127
+ function List({ apps, filter }) {
128
+ return (
129
+ <div data-test="headerbar-apps-menu-list">
130
+ {apps
131
+ .filter(({ displayName, name }) => {
132
+ const appName = displayName || name
133
+ const formattedAppName = appName.toLowerCase()
134
+ const formattedFilter =
135
+ escapeRegExpCharacters(filter).toLowerCase()
136
+
137
+ return filter.length > 0
138
+ ? formattedAppName.match(formattedFilter)
139
+ : true
140
+ })
141
+ .map(({ displayName, name, defaultAction, icon }, idx) => (
142
+ <Item
143
+ key={`app-${name}-${idx}`}
144
+ name={displayName || name}
145
+ path={defaultAction}
146
+ img={icon}
147
+ />
148
+ ))}
149
+
150
+ <style jsx>{`
151
+ div {
152
+ display: flex;
153
+ flex-direction: row;
154
+ flex-wrap: wrap;
155
+ align-content: flex-start;
156
+ align-items: flex-start;
157
+ justify-content: flex-start;
158
+ width: 30vw;
159
+ min-width: 300px;
160
+ max-width: 560px;
161
+
162
+ min-height: 200px;
163
+ max-height: 465px;
164
+ margin-block-start: 0;
165
+ margin-block-end: 8px;
166
+ margin-inline: 8px;
167
+
168
+ overflow: auto;
169
+ overflow-x: hidden;
170
+ }
171
+ `}</style>
172
+ </div>
173
+ )
174
+ }
175
+ List.propTypes = {
176
+ apps: PropTypes.array,
177
+ filter: PropTypes.string,
178
+ }
179
+
180
+ const AppMenu = ({ apps, filter, onFilterChange }) => (
181
+ <div data-test="headerbar-apps-menu">
182
+ <Card>
183
+ <Search value={filter} onChange={onFilterChange} />
184
+ <List apps={apps} filter={filter} />
185
+ </Card>
186
+
187
+ <style jsx>{`
188
+ div {
189
+ z-index: 10000;
190
+ position: absolute;
191
+ inset-inline-end: -4px;
192
+ }
193
+ `}</style>
194
+ </div>
195
+ )
196
+
197
+ AppMenu.propTypes = {
198
+ apps: PropTypes.array.isRequired,
199
+ onFilterChange: PropTypes.func.isRequired,
200
+ filter: PropTypes.string,
201
+ }
202
+
203
+ const Apps = ({ apps }) => {
204
+ const [show, setShow] = useState(false)
205
+ const [filter, setFilter] = useState('')
206
+
207
+ const handleVisibilityToggle = useCallback(() => setShow(!show), [show])
208
+ const handleFilterChange = useCallback(({ value }) => setFilter(value), [])
209
+
210
+ const containerEl = useRef(null)
211
+ const onDocClick = useCallback((evt) => {
212
+ if (containerEl.current && !containerEl.current.contains(evt.target)) {
213
+ setShow(false)
214
+ }
215
+ }, [])
216
+ useEffect(() => {
217
+ document.addEventListener('click', onDocClick)
218
+ return () => document.removeEventListener('click', onDocClick)
219
+ }, [onDocClick])
220
+
221
+ return (
222
+ <div ref={containerEl} data-test="headerbar-apps">
223
+ <button
224
+ onClick={handleVisibilityToggle}
225
+ data-test="headerbar-apps-icon"
226
+ >
227
+ <IconApps24 color={colors.white} />
228
+ </button>
229
+
230
+ {show ? (
231
+ <AppMenu
232
+ apps={apps}
233
+ filter={filter}
234
+ onFilterChange={handleFilterChange}
235
+ />
236
+ ) : null}
237
+
238
+ <style jsx>{`
239
+ button {
240
+ display: block;
241
+ background: transparent;
242
+ padding-block-start: ${spacers.dp4};
243
+ padding-block-end: 0;
244
+ padding-inline: ${spacers.dp12};
245
+ border: 0;
246
+ cursor: pointer;
247
+ height: 100%;
248
+ }
249
+ button:focus {
250
+ outline: 2px solid white;
251
+ outline-offset: -2px;
252
+ }
253
+ button:focus:not(:focus-visible) {
254
+ outline: none;
255
+ }
256
+ button:hover {
257
+ background: #1a557f;
258
+ }
259
+ button:active {
260
+ background: #104067;
261
+ }
262
+
263
+ div {
264
+ position: relative;
265
+ height: 100%;
266
+ }
267
+ `}</style>
268
+ </div>
269
+ )
270
+ }
271
+
272
+ Apps.propTypes = {
273
+ apps: PropTypes.array.isRequired,
274
+ }
275
+
276
+ export default Apps
@@ -0,0 +1,72 @@
1
+ import { colors } from '@dhis2/ui-constants'
2
+ import { MenuItem } from '@dhis2-ui/menu'
3
+ import PropTypes from 'prop-types'
4
+ import React from 'react'
5
+ import i18n from '../locales/index.js'
6
+ import { useDebugInfo } from './use-debug-info.js'
7
+
8
+ export const DebugInfoMenuItem = ({ hideProfileMenu, showDebugInfoModal }) => {
9
+ const debugInfo = useDebugInfo()
10
+
11
+ const openDebugModal = () => {
12
+ hideProfileMenu()
13
+ showDebugInfoModal()
14
+ }
15
+
16
+ const debugInfoLabel = (
17
+ <div className="root">
18
+ <div
19
+ className="instance-info version"
20
+ data-test="dhis2-ui-headerbar-instanceinfo"
21
+ >
22
+ {debugInfo.dhis2_version
23
+ ? i18n.t('DHIS2 {{dhis2Version}}', {
24
+ dhis2Version: debugInfo.dhis2_version,
25
+ })
26
+ : i18n.t('DHIS2 version unknown')}
27
+ </div>
28
+ <div className="version" data-test="dhis2-ui-headerbar-appinfo">
29
+ {debugInfo.app_name
30
+ ? debugInfo.app_version
31
+ ? `${debugInfo.app_name} ${debugInfo.app_version}`
32
+ : i18n.t('{{appName}} version unknown', {
33
+ appName: debugInfo.app_name,
34
+ })
35
+ : debugInfo.app_version
36
+ ? i18n.t('App {{appVersion}}', {
37
+ appVersion: debugInfo.app_version,
38
+ })
39
+ : i18n.t('App version unknown')}
40
+ {}
41
+ </div>
42
+ <style jsx>{`
43
+ .root {
44
+ color: ${colors.grey700};
45
+ font-style: italic;
46
+ font-size: 14px;
47
+ line-height: 17px;
48
+ }
49
+ .instance-info {
50
+ margin-bottom: 4px;
51
+ }
52
+ .version {
53
+ white-space: no-wrap;
54
+ }
55
+ `}</style>
56
+ </div>
57
+ )
58
+
59
+ return (
60
+ <MenuItem
61
+ dense
62
+ onClick={openDebugModal}
63
+ label={debugInfoLabel}
64
+ dataTest="dhis2-ui-headerbar-debuginfo"
65
+ />
66
+ )
67
+ }
68
+
69
+ DebugInfoMenuItem.propTypes = {
70
+ hideProfileMenu: PropTypes.func.isRequired,
71
+ showDebugInfoModal: PropTypes.func.isRequired,
72
+ }
@@ -0,0 +1,47 @@
1
+ import { useAlert } from '@dhis2/app-runtime'
2
+ import { Button, ButtonStrip } from '@dhis2-ui/button'
3
+ import { Modal, ModalActions, ModalContent, ModalTitle } from '@dhis2-ui/modal'
4
+ import PropTypes from 'prop-types'
5
+ import React from 'react'
6
+ import i18n from '../locales/index.js'
7
+ import { DebugInfoTable } from './debug-info-table.js'
8
+ import { useFormattedDebugInfo } from './use-debug-info.js'
9
+
10
+ export function DebugInfoModal({ onClose }) {
11
+ const debugInfo = useFormattedDebugInfo()
12
+ const { show: showClipboardAlert } = useAlert(
13
+ 'Debug information copied to clipboard',
14
+ { duration: 3000 }
15
+ )
16
+
17
+ const copyDebugInfo = () => {
18
+ navigator.clipboard.writeText(debugInfo)
19
+ onClose()
20
+ showClipboardAlert()
21
+ }
22
+
23
+ return (
24
+ <Modal position="middle" dataTest="dhis2-ui-headerbar-debuginfomodal">
25
+ <ModalTitle>{i18n.t('Debug info')}</ModalTitle>
26
+ <ModalContent>
27
+ <DebugInfoTable />
28
+ </ModalContent>
29
+ <ModalActions>
30
+ <ButtonStrip end>
31
+ <Button onClick={() => onClose()}>{i18n.t('Close')}</Button>
32
+ <Button
33
+ primary
34
+ onClick={copyDebugInfo}
35
+ dataTest="dhis2-ui-headerbar-debuginfomodal-copybutton"
36
+ >
37
+ {i18n.t('Copy debug info')}
38
+ </Button>
39
+ </ButtonStrip>
40
+ </ModalActions>
41
+ </Modal>
42
+ )
43
+ }
44
+
45
+ DebugInfoModal.propTypes = {
46
+ onClose: PropTypes.func.isRequired,
47
+ }
@@ -0,0 +1,51 @@
1
+ import { colors } from '@dhis2/ui-constants'
2
+ import React from 'react'
3
+ import { useDebugInfo } from './use-debug-info.js'
4
+
5
+ const formatDebugInfoKey = (key) => {
6
+ const tokens = key.split('_')
7
+ return tokens
8
+ .map((token) => {
9
+ if (token.toLowerCase() === 'dhis2') {
10
+ return 'DHIS2'
11
+ } else {
12
+ return token[0].toUpperCase() + token.substr(1).toLowerCase()
13
+ }
14
+ })
15
+ .join(' ')
16
+ }
17
+
18
+ export function DebugInfoTable() {
19
+ const debugInfo = useDebugInfo()
20
+ return (
21
+ <table {...{ 'data-test': 'dhis2-ui-headerbar-debuginfotable' }}>
22
+ <tbody>
23
+ {Object.keys(debugInfo).map((key) => (
24
+ <tr key={key}>
25
+ <td className="debug-info-key">
26
+ {formatDebugInfoKey(key)}
27
+ </td>
28
+ <td>{debugInfo[key]}</td>
29
+ </tr>
30
+ ))}
31
+ </tbody>
32
+ <style jsx>{`
33
+ table {
34
+ white-space: pre-wrap;
35
+ font-size: 14px;
36
+ line-height: 1.2;
37
+ color: ${colors.grey700};
38
+ font-famile: Menlo, Courier, monospace !important;
39
+ }
40
+ td {
41
+ padding-block: 3px;
42
+ padding-inline-end: 16px;
43
+ padding-inline-start: 0;
44
+ }
45
+ .debug-info-key {
46
+ font-weight: bold;
47
+ }
48
+ `}</style>
49
+ </table>
50
+ )
51
+ }