@sap-ux/control-property-editor 0.2.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 (112) hide show
  1. package/.eslintignore +1 -0
  2. package/.eslintrc.js +16 -0
  3. package/CHANGELOG.md +7 -0
  4. package/LICENSE +201 -0
  5. package/README.md +16 -0
  6. package/dist/app.css +2 -0
  7. package/dist/app.css.map +7 -0
  8. package/dist/app.js +347 -0
  9. package/dist/app.js.map +7 -0
  10. package/esbuild.js +25 -0
  11. package/jest.config.js +20 -0
  12. package/package.json +68 -0
  13. package/src/App.scss +57 -0
  14. package/src/App.tsx +136 -0
  15. package/src/Workarounds.scss +79 -0
  16. package/src/actions.ts +3 -0
  17. package/src/components/AppLogo.module.scss +8 -0
  18. package/src/components/AppLogo.tsx +75 -0
  19. package/src/components/ChangeIndicator.tsx +80 -0
  20. package/src/components/Separator.tsx +32 -0
  21. package/src/components/ThemeSelectorCallout.scss +48 -0
  22. package/src/components/ThemeSelectorCallout.tsx +125 -0
  23. package/src/components/ToolBar.scss +39 -0
  24. package/src/components/ToolBar.tsx +26 -0
  25. package/src/components/index.ts +4 -0
  26. package/src/devices.ts +18 -0
  27. package/src/global.d.ts +4 -0
  28. package/src/i18n/i18n.json +68 -0
  29. package/src/i18n.ts +25 -0
  30. package/src/icons.tsx +198 -0
  31. package/src/index.css +1288 -0
  32. package/src/index.tsx +47 -0
  33. package/src/middleware.ts +54 -0
  34. package/src/panels/LeftPanel.scss +17 -0
  35. package/src/panels/LeftPanel.tsx +48 -0
  36. package/src/panels/changes/ChangeStack.module.scss +3 -0
  37. package/src/panels/changes/ChangeStack.tsx +219 -0
  38. package/src/panels/changes/ChangeStackHeader.tsx +43 -0
  39. package/src/panels/changes/ChangesPanel.module.scss +18 -0
  40. package/src/panels/changes/ChangesPanel.tsx +90 -0
  41. package/src/panels/changes/ControlGroup.module.scss +17 -0
  42. package/src/panels/changes/ControlGroup.tsx +61 -0
  43. package/src/panels/changes/PropertyChange.module.scss +24 -0
  44. package/src/panels/changes/PropertyChange.tsx +159 -0
  45. package/src/panels/changes/UnknownChange.module.scss +46 -0
  46. package/src/panels/changes/UnknownChange.tsx +96 -0
  47. package/src/panels/changes/index.tsx +3 -0
  48. package/src/panels/changes/utils.ts +36 -0
  49. package/src/panels/index.ts +2 -0
  50. package/src/panels/outline/Funnel.tsx +64 -0
  51. package/src/panels/outline/NoControlFound.tsx +45 -0
  52. package/src/panels/outline/OutlinePanel.scss +98 -0
  53. package/src/panels/outline/OutlinePanel.tsx +38 -0
  54. package/src/panels/outline/Tree.tsx +393 -0
  55. package/src/panels/outline/index.ts +1 -0
  56. package/src/panels/outline/utils.ts +154 -0
  57. package/src/panels/properties/Clipboard.tsx +44 -0
  58. package/src/panels/properties/DeviceSelector.tsx +40 -0
  59. package/src/panels/properties/DeviceToggle.tsx +39 -0
  60. package/src/panels/properties/DropdownEditor.tsx +80 -0
  61. package/src/panels/properties/Funnel.tsx +64 -0
  62. package/src/panels/properties/HeaderField.tsx +150 -0
  63. package/src/panels/properties/IconValueHelp.tsx +203 -0
  64. package/src/panels/properties/InputTypeSelector.tsx +20 -0
  65. package/src/panels/properties/InputTypeToggle.module.scss +4 -0
  66. package/src/panels/properties/InputTypeToggle.tsx +79 -0
  67. package/src/panels/properties/InputTypeWrapper.tsx +259 -0
  68. package/src/panels/properties/NoControlSelected.tsx +38 -0
  69. package/src/panels/properties/Properties.scss +102 -0
  70. package/src/panels/properties/PropertiesList.tsx +162 -0
  71. package/src/panels/properties/PropertiesPanel.tsx +30 -0
  72. package/src/panels/properties/PropertyDocumentation.module.scss +81 -0
  73. package/src/panels/properties/PropertyDocumentation.tsx +174 -0
  74. package/src/panels/properties/SapUiIcon.scss +109 -0
  75. package/src/panels/properties/StringEditor.tsx +122 -0
  76. package/src/panels/properties/ViewChanger.module.scss +5 -0
  77. package/src/panels/properties/ViewChanger.tsx +143 -0
  78. package/src/panels/properties/constants.ts +2 -0
  79. package/src/panels/properties/index.tsx +1 -0
  80. package/src/panels/properties/propertyValuesCache.ts +39 -0
  81. package/src/panels/properties/types.ts +49 -0
  82. package/src/slice.ts +216 -0
  83. package/src/store.ts +19 -0
  84. package/src/use-local-storage.ts +40 -0
  85. package/src/use-window-size.ts +39 -0
  86. package/src/variables.scss +2 -0
  87. package/test/unit/App.test.tsx +207 -0
  88. package/test/unit/appIndex.test.ts +23 -0
  89. package/test/unit/components/ChangeIndicator.test.tsx +120 -0
  90. package/test/unit/components/ThemeSelector.test.tsx +41 -0
  91. package/test/unit/middleware.test.ts +116 -0
  92. package/test/unit/panels/changes/ChangesPanel.test.tsx +261 -0
  93. package/test/unit/panels/changes/utils.test.ts +40 -0
  94. package/test/unit/panels/outline/OutlinePanel.test.tsx +353 -0
  95. package/test/unit/panels/outline/__snapshots__/utils.test.ts.snap +36 -0
  96. package/test/unit/panels/outline/utils.test.ts +83 -0
  97. package/test/unit/panels/properties/Clipboard.test.tsx +18 -0
  98. package/test/unit/panels/properties/DropdownEditor.test.tsx +62 -0
  99. package/test/unit/panels/properties/Funnel.test.tsx +34 -0
  100. package/test/unit/panels/properties/HeaderField.test.tsx +36 -0
  101. package/test/unit/panels/properties/IconValueHelp.test.tsx +60 -0
  102. package/test/unit/panels/properties/InputTypeToggle.test.tsx +126 -0
  103. package/test/unit/panels/properties/InputTypeWrapper.test.tsx +430 -0
  104. package/test/unit/panels/properties/PropertyDocumentation.test.tsx +131 -0
  105. package/test/unit/panels/properties/StringEditor.test.tsx +107 -0
  106. package/test/unit/panels/properties/ViewChanger.test.tsx +190 -0
  107. package/test/unit/panels/properties/propertyValuesCache.test.ts +23 -0
  108. package/test/unit/slice.test.ts +268 -0
  109. package/test/unit/utils.tsx +67 -0
  110. package/test/utils/utils.tsx +25 -0
  111. package/tsconfig.eslint.json +4 -0
  112. package/tsconfig.json +39 -0
@@ -0,0 +1,159 @@
1
+ import type { ReactElement } from 'react';
2
+ import React, { useState } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { useDispatch } from 'react-redux';
5
+
6
+ import { Stack, StackItem, Text } from '@fluentui/react';
7
+ import { UIIcon, UIIconButton, UiIcons, UIDialog } from '@sap-ux/ui-components';
8
+
9
+ import type { PropertyChangeDeletionDetails } from '@sap-ux-private/control-property-editor-common';
10
+ import { convertCamelCaseToPascalCase, deletePropertyChanges } from '@sap-ux-private/control-property-editor-common';
11
+ import { IconName } from '../../icons';
12
+
13
+ import styles from './PropertyChange.module.scss';
14
+ import { getFormattedDateAndTime } from './utils';
15
+
16
+ export interface PropertyChangeProps {
17
+ controlId: string;
18
+ controlName: string;
19
+ changeIndex: number;
20
+ propertyName: string;
21
+ value: string | number | boolean;
22
+ isActive: boolean;
23
+ timestamp?: number;
24
+ fileName?: string;
25
+ /**
26
+ * Class used for showing and hiding actions
27
+ */
28
+ actionClassName: string;
29
+ }
30
+
31
+ /**
32
+ * React element for property change.
33
+ *
34
+ * @param propertyChangeProps PropertyChangeProps
35
+ * @returns ReactElement
36
+ */
37
+ export function PropertyChange(propertyChangeProps: PropertyChangeProps): ReactElement {
38
+ const { controlId, propertyName, value, isActive, timestamp, fileName, actionClassName } = propertyChangeProps;
39
+ const { t } = useTranslation();
40
+ const dispatch = useDispatch();
41
+ const [dialogState, setDialogState] = useState<PropertyChangeDeletionDetails | undefined>(undefined);
42
+
43
+ const valueIcon = getValueIcon(value);
44
+
45
+ function onConfirmDelete(): void {
46
+ if (dialogState) {
47
+ dispatch(deletePropertyChanges(dialogState));
48
+ setDialogState(undefined);
49
+ }
50
+ }
51
+
52
+ function onCancelDelete(): void {
53
+ setDialogState(undefined);
54
+ }
55
+
56
+ return (
57
+ <>
58
+ <Stack
59
+ tokens={{
60
+ childrenGap: 5
61
+ }}
62
+ className={styles.container}
63
+ style={{
64
+ opacity: isActive ? 1 : 0.4
65
+ }}>
66
+ <Stack.Item className={styles.property}>
67
+ <Stack
68
+ horizontal
69
+ horizontalAlign={'space-between'}
70
+ tokens={{
71
+ childrenGap: 5
72
+ }}>
73
+ <Stack.Item>
74
+ <Text className={styles.text}>{convertCamelCaseToPascalCase(propertyName)}</Text>
75
+ <UIIcon iconName={IconName.arrow} className={styles.text} />
76
+ {valueIcon && (
77
+ <UIIcon
78
+ className={styles.valueIcon}
79
+ iconName={valueIcon}
80
+ style={{
81
+ marginRight: 5,
82
+ background: 'var(--vscode-terminal-ansiBlue)',
83
+ borderRadius: '50%',
84
+ height: 16,
85
+ width: 16,
86
+ verticalAlign: 'bottom'
87
+ }}
88
+ />
89
+ )}
90
+ <Text className={styles.text}>{value}</Text>
91
+ </Stack.Item>
92
+ {fileName && (
93
+ <Stack.Item className={actionClassName}>
94
+ <UIIconButton
95
+ iconProps={{ iconName: UiIcons.TrashCan }}
96
+ onClick={(): void => {
97
+ if (controlId) {
98
+ setDialogState({
99
+ controlId,
100
+ propertyName,
101
+ fileName
102
+ });
103
+ }
104
+ }}
105
+ />
106
+ </Stack.Item>
107
+ )}
108
+ </Stack>
109
+ </Stack.Item>
110
+
111
+ {timestamp && (
112
+ <StackItem>
113
+ <Stack horizontal horizontalAlign="space-between">
114
+ <Text className={styles.timestamp}>{getFormattedDateAndTime(timestamp)}</Text>
115
+ </Stack>
116
+ </StackItem>
117
+ )}
118
+ </Stack>
119
+ {dialogState && (
120
+ <UIDialog
121
+ hidden={dialogState === undefined}
122
+ onAccept={onConfirmDelete}
123
+ acceptButtonText={t('CONFIRM_DELETE')}
124
+ cancelButtonText={t('CANCEL_DELETE')}
125
+ onCancel={onCancelDelete}
126
+ dialogContentProps={{
127
+ title: t('CONFIRM_DELETE_TITLE'),
128
+ subText: t('CONFIRM_CHANGE_SUMMARY_DELETE_SUBTEXT')
129
+ }}
130
+ />
131
+ )}
132
+ </>
133
+ );
134
+ }
135
+
136
+ /**
137
+ * Get value icon based on type.
138
+ *
139
+ * @param value string | number | boolean
140
+ * @returns string | undefined
141
+ */
142
+ function getValueIcon(value: string | number | boolean): string | undefined {
143
+ if (typeof value === 'string') {
144
+ if (value.trim().startsWith('{') && value.trim().endsWith('}')) {
145
+ return IconName.expression;
146
+ } else {
147
+ return IconName.string;
148
+ }
149
+ } else if (typeof value === 'number') {
150
+ return IconName.number;
151
+ } else if (typeof value === 'boolean') {
152
+ if (value === true) {
153
+ return IconName.boolTrue;
154
+ } else {
155
+ return IconName.boolFalse;
156
+ }
157
+ }
158
+ return undefined;
159
+ }
@@ -0,0 +1,46 @@
1
+ .item {
2
+ padding: 5px 15px 5px 15px;
3
+ }
4
+ .text {
5
+ line-height: 18px;
6
+ display: inline-block;
7
+ text-overflow: ellipsis;
8
+ overflow: hidden;
9
+ white-space: nowrap;
10
+ color: var(--vscode-editor-foreground);
11
+ width: 240px;
12
+ }
13
+ .timestamp {
14
+ color: var(--vscode-editor-foreground);
15
+ font-size: 11px;
16
+ line-height: 15px;
17
+ opacity: 0.5;
18
+
19
+ }
20
+ .textHeader {
21
+ display: inline-block;
22
+ color: var(--vscode-textLink-foreground);
23
+ font-size: 13px;
24
+ font-weight: bold;
25
+ text-overflow: ellipsis;
26
+ white-space: nowrap;
27
+ overflow: hidden;
28
+ line-height: 18px;
29
+ }
30
+
31
+ .property {
32
+ overflow-wrap: anywhere;
33
+ }
34
+
35
+ .item:hover {
36
+ background-color: var(--vscode-dropdown-background);
37
+ color: var(--vscode-dropdown-foreground);
38
+ outline: 1px dashed var(--vscode-contrastActiveBorder);
39
+ .actions {
40
+ visibility: visible;
41
+ }
42
+ }
43
+
44
+ .actions {
45
+ visibility: hidden;
46
+ }
@@ -0,0 +1,96 @@
1
+ import type { ReactElement } from 'react';
2
+ import React, { useState } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Text, Stack } from '@fluentui/react';
5
+ import { useDispatch } from 'react-redux';
6
+ import styles from './UnknownChange.module.scss';
7
+ import { UIIconButton, UiIcons, UIDialog } from '@sap-ux/ui-components';
8
+ import type { PropertyChangeDeletionDetails } from '@sap-ux-private/control-property-editor-common';
9
+ import { deletePropertyChanges, convertCamelCaseToPascalCase } from '@sap-ux-private/control-property-editor-common';
10
+ import { getFormattedDateAndTime } from './utils';
11
+
12
+ export interface UnknownChangeProps {
13
+ fileName: string;
14
+ timestamp?: number;
15
+ }
16
+
17
+ /**
18
+ * React element for unknown change in change stack.
19
+ *
20
+ * @param unknownChangeProps UnknownChangeProps
21
+ * @returns ReactElement
22
+ */
23
+ export function UnknownChange(unknownChangeProps: UnknownChangeProps): ReactElement {
24
+ const { fileName, timestamp } = unknownChangeProps;
25
+ const { t } = useTranslation();
26
+ const dispatch = useDispatch();
27
+ const [dialogState, setDialogState] = useState<PropertyChangeDeletionDetails | undefined>(undefined);
28
+
29
+ function onConfirmDelete(): void {
30
+ if (dialogState) {
31
+ dispatch(deletePropertyChanges(dialogState));
32
+ setDialogState(undefined);
33
+ }
34
+ }
35
+
36
+ function onCancelDelete(): void {
37
+ setDialogState(undefined);
38
+ }
39
+
40
+ const parts = fileName.split('_');
41
+ const changeName = parts[parts.length - 1];
42
+ const name = convertCamelCaseToPascalCase(changeName);
43
+ return (
44
+ <>
45
+ <Stack className={styles.item}>
46
+ <Stack.Item className={styles.property}>
47
+ <Stack horizontal>
48
+ <Stack.Item>
49
+ <Text className={styles.textHeader}>
50
+ {name} {t('CHANGE')}
51
+ </Text>
52
+ <Text className={styles.text} title={fileName}>
53
+ {t('FILE')}
54
+ {fileName}
55
+ </Text>
56
+ </Stack.Item>
57
+ {fileName && (
58
+ <Stack.Item className={styles.actions}>
59
+ <UIIconButton
60
+ iconProps={{ iconName: UiIcons.TrashCan }}
61
+ onClick={(): void => {
62
+ setDialogState({
63
+ controlId: '',
64
+ propertyName: '',
65
+ fileName
66
+ });
67
+ }}
68
+ />
69
+ </Stack.Item>
70
+ )}
71
+ </Stack>
72
+ </Stack.Item>
73
+ {timestamp && (
74
+ <Stack.Item>
75
+ <Stack horizontal horizontalAlign="space-between">
76
+ <Text className={styles.timestamp}>{getFormattedDateAndTime(timestamp)}</Text>
77
+ </Stack>
78
+ </Stack.Item>
79
+ )}
80
+ </Stack>
81
+ {dialogState && (
82
+ <UIDialog
83
+ hidden={dialogState === undefined}
84
+ onAccept={onConfirmDelete}
85
+ acceptButtonText={t('CONFIRM_DELETE')}
86
+ cancelButtonText={t('CANCEL_DELETE')}
87
+ onCancel={onCancelDelete}
88
+ dialogContentProps={{
89
+ title: t('CONFIRM_DELETE_TITLE'),
90
+ subText: t('CONFIRM_CHANGE_SUMMARY_DELETE_SUBTEXT')
91
+ }}
92
+ />
93
+ )}
94
+ </>
95
+ );
96
+ }
@@ -0,0 +1,3 @@
1
+ export { isKnownChange } from './ChangeStack';
2
+ export { ChangesPanel } from './ChangesPanel';
3
+ export { ControlGroupProps, ControlPropertyChange } from './ControlGroup';
@@ -0,0 +1,36 @@
1
+ const localeTimeOptions: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit', hour12: false };
2
+ const localeDateOptions: Intl.DateTimeFormatOptions = { day: '2-digit', month: '2-digit', year: '2-digit' };
3
+
4
+ /**
5
+ * Gets locale.
6
+ *
7
+ * @returns string
8
+ */
9
+ function getLocale(): string {
10
+ if (globalThis?.navigator?.languages) {
11
+ const supportedLocales = Intl.DateTimeFormat.supportedLocalesOf([...globalThis.navigator.languages], {
12
+ ...localeDateOptions,
13
+ ...localeTimeOptions
14
+ });
15
+ if (supportedLocales.length) {
16
+ return supportedLocales[0];
17
+ }
18
+ }
19
+ return 'en-GB';
20
+ }
21
+
22
+ /**
23
+ * Gets formatted date time based on locale.
24
+ *
25
+ * @param timestamp number
26
+ * @returns string
27
+ */
28
+ export function getFormattedDateAndTime(timestamp: number): string {
29
+ const date = new Date(timestamp);
30
+ const locale = getLocale();
31
+
32
+ return `${date.toLocaleTimeString(locale, localeTimeOptions)} ${date.toLocaleDateString(
33
+ locale,
34
+ localeDateOptions
35
+ )}`;
36
+ }
@@ -0,0 +1,2 @@
1
+ export { PropertiesPanel } from './properties';
2
+ export { LeftPanel } from './LeftPanel';
@@ -0,0 +1,64 @@
1
+ import type { ReactElement } from 'react';
2
+ import React, { useState } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { UICheckbox, UIIconButton, UICallout, UICalloutContentPadding } from '@sap-ux/ui-components';
5
+ import { useDispatch, useSelector } from 'react-redux';
6
+ import { IconName } from '../../icons';
7
+ import type { FilterOptions } from '../../slice';
8
+ import { FilterName, filterNodes } from '../../slice';
9
+ import type { RootState } from '../../store';
10
+
11
+ const TARGET = 'control-property-editor-funnel-callout-target-id';
12
+
13
+ const Funnel = (): ReactElement => {
14
+ const { t } = useTranslation();
15
+ const filterQuery = useSelector<RootState, FilterOptions[]>((state) => state.filterQuery);
16
+ const dispatch = useDispatch();
17
+ const [isVisible, setIsVisible] = useState(false);
18
+ const showCallout = () => {
19
+ setIsVisible(true);
20
+ };
21
+ const onChange = (name: FilterName, isChecked = false) => {
22
+ const action = filterNodes([{ name, value: isChecked }]);
23
+ dispatch(action);
24
+ };
25
+ const focusChecked = filterQuery.filter((item) => item.name === FilterName.focusEditable)[0].value as boolean;
26
+ const focusCommonlyUsedChecked = filterQuery.filter((item) => item.name === FilterName.focusCommonlyUsed)[0]
27
+ .value as boolean;
28
+ const checked = focusChecked || focusCommonlyUsedChecked;
29
+ return (
30
+ <>
31
+ <UIIconButton
32
+ id={TARGET}
33
+ className={`funnel-icon`}
34
+ iconProps={{ iconName: IconName.funnel }}
35
+ checked={checked}
36
+ onClick={showCallout}></UIIconButton>
37
+ {isVisible && (
38
+ <UICallout
39
+ target={`#${TARGET}`}
40
+ isBeakVisible={true}
41
+ beakWidth={5}
42
+ directionalHint={4}
43
+ onDismiss={() => setIsVisible(false)}
44
+ contentPadding={UICalloutContentPadding.Standard}>
45
+ <UICheckbox
46
+ className={'funnel-call-out-checkbox'}
47
+ label={t('FOCUS_EDITABLE')}
48
+ checked={focusChecked}
49
+ onChange={(ev?: React.FormEvent<HTMLElement | HTMLInputElement>, isChecked?: boolean) => {
50
+ onChange(FilterName.focusEditable, isChecked);
51
+ }}></UICheckbox>
52
+ <UICheckbox
53
+ label={t('FOCUS_COMMONLY_USED')}
54
+ checked={focusCommonlyUsedChecked}
55
+ onChange={(ev?: React.FormEvent<HTMLElement | HTMLInputElement>, isChecked?: boolean) => {
56
+ onChange(FilterName.focusCommonlyUsed, isChecked);
57
+ }}></UICheckbox>
58
+ </UICallout>
59
+ )}
60
+ </>
61
+ );
62
+ };
63
+
64
+ export { Funnel };
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { Text } from '@fluentui/react';
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ const NoControlFound = () => {
6
+ const { t } = useTranslation();
7
+ return (
8
+ <>
9
+ <Text className="tree-no-control-found">{t('NO_CONTROL_FOUND')}</Text>
10
+ <Text className="tree-modify-search-input">{t('MODIFY_SEARCH_INPUT')}</Text>
11
+ <div className="tree-search-icon">
12
+ <svg
13
+ data-testid="Control-Property-Editor-No-Search-Matched-Icon"
14
+ width="49"
15
+ height="49"
16
+ viewBox="0 0 49 49"
17
+ fill="none"
18
+ xmlns="http://www.w3.org/2000/svg">
19
+ <path
20
+ fillRule="evenodd"
21
+ clipRule="evenodd"
22
+ d="M20 40C31.0457 40 40 31.0457 40 20C40 8.9543 31.0457 0 20 0C8.9543 0 0 8.9543 0 20C0 31.0457 8.9543 40 20 40ZM20 37C29.3888 37 37 29.3888 37 20C37 10.6112 29.3888 3 20 3C10.6112 3 3 10.6112 3 20C3 29.3888 10.6112 37 20 37Z"
23
+ fill="var(--vscode-icon-foreground)"
24
+ />
25
+ <rect
26
+ x="33"
27
+ y="35.1213"
28
+ width="3"
29
+ height="19.2712"
30
+ transform="rotate(-45 33 35.1213)"
31
+ fill="var(--vscode-icon-foreground)"
32
+ />
33
+ <rect x="15" y="13" width="2" height="7" fill="var(--vscode-focusBorder)" />
34
+ <rect x="23" y="13" width="2" height="7" fill="var(--vscode-focusBorder)" />
35
+ <path
36
+ d="M12.5815 28C13.7683 25.0682 16.6426 23 20 23C23.3574 23 26.2317 25.0682 27.4185 28H25.1973C24.1599 26.2066 22.2208 25 20 25C17.7792 25 15.8401 26.2066 14.8027 28H12.5815Z"
37
+ fill="var(--vscode-focusBorder)"
38
+ />
39
+ </svg>
40
+ </div>
41
+ </>
42
+ );
43
+ };
44
+
45
+ export { NoControlFound };
@@ -0,0 +1,98 @@
1
+ .filter-outline {
2
+ display: flex;
3
+ margin: 20px 14px 30px 14px;
4
+ flex-direction: row;
5
+ align-items: center;
6
+ overflow-y: auto;
7
+ }
8
+
9
+ .funnel-icon {
10
+ margin-left: 16px;
11
+ i {
12
+ line-height: 20px;
13
+ }
14
+ }
15
+
16
+ .funnel-call-out-checkbox {
17
+ padding-bottom: 5px;
18
+ }
19
+
20
+ .tree-no-control-found {
21
+ font-style: normal;
22
+ font-weight: bold;
23
+ font-size: 24px;
24
+ line-height: 29px;
25
+ text-align: center;
26
+ color: var(--vscode-foreground);
27
+ }
28
+ .tree-modify-search-input {
29
+ font-style: normal;
30
+ font-weight: normal;
31
+ font-size: 13px;
32
+ line-height: 18px;
33
+ text-align: center;
34
+ padding-top: 20px;
35
+ color: var(--vscode-foreground);
36
+ }
37
+ .tree-search-icon {
38
+ text-align: center;
39
+ }
40
+
41
+ .tree-cell {
42
+ display: flex;
43
+ font-size: 13px;
44
+ font-weight: normal;
45
+ white-space: nowrap;
46
+ text-overflow: ellipsis;
47
+ height: 18px;
48
+ padding: 5px 0;
49
+ min-width: 0px;
50
+ align-items: center;
51
+ }
52
+
53
+ .tree-row {
54
+ display: flex;
55
+ justify-content: space-between;
56
+ &:hover {
57
+ cursor: pointer;
58
+ background-color: var(--vscode-dropdown-background);
59
+ color: var(--vscode-dropdown-foreground);
60
+ outline: 1px dashed var(--vscode-contrastActiveBorder);
61
+ }
62
+ align-items: center;
63
+ }
64
+
65
+ .app-panel-scroller > div {
66
+ padding-top: 0px;
67
+ }
68
+ .focusEditable {
69
+ opacity: 0.55;
70
+ }
71
+
72
+ .tree-row-icon {
73
+ display: flex;
74
+ flex-wrap: nowrap;
75
+ flex-shrink: 0;
76
+ position: relative;
77
+ }
78
+
79
+ .right-chevron-icon {
80
+ margin-right: 3px;
81
+ transform: rotate(0deg);
82
+ transform-origin: 50% 50%;
83
+ transition: transform 0.1s linear 0s;
84
+ }
85
+ .down-chevron-icon {
86
+ margin-right: 3px;
87
+ transform: rotate(90deg);
88
+ transform-origin: 50% 50%;
89
+ transition: transform 0.1s linear 0s;
90
+ }
91
+
92
+ .app-panel-selected-bg.tree-row {
93
+ color: var(--vscode-list-activeSelectionForeground);
94
+ background-color: var(--vscode-list-activeSelectionBackground);
95
+ outline: 1px solid var(--vscode-contrastActiveBorder);
96
+ }
97
+
98
+
@@ -0,0 +1,38 @@
1
+ import type { ReactElement } from 'react';
2
+ import React from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { useDispatch } from 'react-redux';
5
+ import { UISearchBox } from '@sap-ux/ui-components';
6
+ import { FilterName, filterNodes } from '../../slice';
7
+ import './OutlinePanel.scss';
8
+
9
+ import { Tree } from './Tree';
10
+ import { Funnel } from './Funnel';
11
+
12
+ const OutlinePanel = (): ReactElement => {
13
+ const dispatch = useDispatch();
14
+ const { t } = useTranslation();
15
+ const onFilterChange = (
16
+ event?: React.ChangeEvent<HTMLInputElement> | undefined,
17
+ newValue?: string | undefined
18
+ ): void => {
19
+ const action = filterNodes([{ name: FilterName.query, value: newValue ?? '' }]);
20
+ dispatch(action);
21
+ };
22
+ return (
23
+ <>
24
+ <div className="filter-outline">
25
+ <UISearchBox
26
+ autoFocus={false}
27
+ disableAnimation={false}
28
+ placeholder={t('SEARCH_OUTLINE')}
29
+ onChange={onFilterChange}
30
+ />
31
+ <Funnel />
32
+ </div>
33
+ <Tree />
34
+ </>
35
+ );
36
+ };
37
+
38
+ export { OutlinePanel };