@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,80 @@
1
+ import type { ReactElement } from 'react';
2
+ import React from 'react';
3
+ import { useDispatch } from 'react-redux';
4
+
5
+ import type { UIComboBoxOption, UIComboBoxRef } from '@sap-ux/ui-components';
6
+ import { UIComboBox } from '@sap-ux/ui-components';
7
+
8
+ import type { PropertyChange, StringControlPropertyWithOptions } from '@sap-ux-private/control-property-editor-common';
9
+ import { changeProperty } from '../../slice';
10
+
11
+ import { setCachedValue } from './propertyValuesCache';
12
+ import type { PropertyInputProps } from './types';
13
+ import { InputType } from './types';
14
+
15
+ import './Properties.scss';
16
+ import { debounce, reportTelemetry } from '@sap-ux-private/control-property-editor-common';
17
+
18
+ // exported to make it testable without events
19
+ export const valueChanged = (
20
+ controlId: string,
21
+ name: string,
22
+ newValue: string | number,
23
+ controlName: string
24
+ ): { payload: PropertyChange<string | number | boolean>; type: string } => {
25
+ setCachedValue(controlId, name, InputType.enumMember, newValue);
26
+ return changeProperty({ controlId, propertyName: name, value: newValue, controlName });
27
+ };
28
+
29
+ /**
30
+ * React element for dropdown editor.
31
+ *
32
+ * @param propertyInputProps PropertyInputProps<StringControlPropertyWithOptions>
33
+ * @returns ReactElement
34
+ */
35
+ export function DropdownEditor(propertyInputProps: PropertyInputProps<StringControlPropertyWithOptions>): ReactElement {
36
+ const {
37
+ property: { name, value, options, isEnabled, errorMessage },
38
+ controlId,
39
+ controlName
40
+ } = propertyInputProps;
41
+ const dispatch = useDispatch();
42
+ const selectedOption = options.find((enumValue) => enumValue.key === value);
43
+ const text = !selectedOption && value ? value : undefined;
44
+
45
+ return (
46
+ <UIComboBox
47
+ className="dropdownEditor"
48
+ key={name}
49
+ data-testid={`${name}--DropdownEditor`}
50
+ selectedKey={value}
51
+ disabled={!isEnabled}
52
+ autoComplete="on"
53
+ allowFreeform={true}
54
+ text={text}
55
+ errorMessage={errorMessage}
56
+ options={options}
57
+ useComboBoxAsMenuWidth={true}
58
+ onChange={(
59
+ event: React.FormEvent<UIComboBoxRef>,
60
+ option?: UIComboBoxOption,
61
+ index?: number,
62
+ value?: string
63
+ ): void => {
64
+ const newValue = option?.key ?? value ?? '';
65
+ reportTelemetry({ category: 'Property Change', propertyName: name }).catch((error) => {
66
+ console.error(`Error in reporting telemetry`, error);
67
+ });
68
+ dispatch(valueChanged(controlId, name, newValue, controlName));
69
+ }}
70
+ onPendingValueChanged={debounce((option?: UIComboBoxOption, index?: number, value?: string): void => {
71
+ if (value) {
72
+ reportTelemetry({ category: 'Property Change', propertyName: name }).catch((error) => {
73
+ console.error(`Error in reporting telemetry`, error);
74
+ });
75
+ dispatch(valueChanged(controlId, name, value, controlName));
76
+ }
77
+ }, 500)}
78
+ />
79
+ );
80
+ }
@@ -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
+ import './Properties.scss';
11
+
12
+ const TARGET = 'control-property-editor-property-search-funnel-callout-target-id';
13
+
14
+ const Funnel = (): ReactElement => {
15
+ const { t } = useTranslation();
16
+ const filterQuery = useSelector<RootState, FilterOptions[]>((state) => state.filterQuery);
17
+ const dispatch = useDispatch();
18
+ const [isVisible, setIsVisible] = useState(false);
19
+ const showCallout = () => {
20
+ setIsVisible(true);
21
+ };
22
+ const onChange = (name: FilterName, isChecked = false) => {
23
+ const action = filterNodes([{ name, value: isChecked }]);
24
+ dispatch(action);
25
+ };
26
+ const showEditablePropertiesChecked = filterQuery.filter(
27
+ (item) => item.name === FilterName.showEditableProperties
28
+ )[0].value as boolean;
29
+ const checked = showEditablePropertiesChecked;
30
+ return (
31
+ <>
32
+ <UIIconButton
33
+ id={TARGET}
34
+ className={`funnel-properties-icon`}
35
+ iconProps={{ iconName: IconName.funnel }}
36
+ checked={checked}
37
+ onClick={showCallout}></UIIconButton>
38
+ {isVisible && (
39
+ <UICallout
40
+ styles={{ calloutMain: { minWidth: 196, minHeight: 35 } }}
41
+ target={`#${TARGET}`}
42
+ isBeakVisible={true}
43
+ gapSpace={5}
44
+ beakWidth={5}
45
+ directionalHint={4}
46
+ onDismiss={() => setIsVisible(false)}
47
+ contentPadding={UICalloutContentPadding.Standard}>
48
+ <UICheckbox
49
+ id={'editable-properties-checkbox'}
50
+ styles={{
51
+ checkbox: { width: 20, height: 20 }
52
+ }}
53
+ label={t('SHOW_EDITABLE_PROPERTIES')}
54
+ checked={showEditablePropertiesChecked}
55
+ onChange={(ev?: React.FormEvent<HTMLElement | HTMLInputElement>, isChecked?: boolean) => {
56
+ onChange(FilterName.showEditableProperties, isChecked);
57
+ }}></UICheckbox>
58
+ </UICallout>
59
+ )}
60
+ </>
61
+ );
62
+ };
63
+
64
+ export { Funnel };
@@ -0,0 +1,150 @@
1
+ import type { ReactElement } from 'react';
2
+ import React, { useCallback, useState } from 'react';
3
+
4
+ import { TextField, Label, Stack } from '@fluentui/react';
5
+ import { UIIconButton, UiIcons, UITooltip, UITooltipUtils, UIDirectionalHint } from '@sap-ux/ui-components';
6
+
7
+ import './Properties.scss';
8
+ import { defaultFontSize, sectionHeaderFontSize } from './constants';
9
+ import { PropertyDocumentation } from './PropertyDocumentation';
10
+ import type { PropertiesInfo } from '@sap-ux-private/control-property-editor-common';
11
+ import { Clipboard } from './Clipboard';
12
+
13
+ export interface HeaderFieldProps {
14
+ label: string;
15
+ /**
16
+ * There will be an icon on the screen that allows to copy this value to clipboard
17
+ */
18
+ value: string;
19
+
20
+ documentation?: PropertiesInfo;
21
+
22
+ hidden?: boolean;
23
+ }
24
+
25
+ /**
26
+ * React element for HeaderField.
27
+ *
28
+ * @param headerFieldProps HeaderFieldProps
29
+ * @returns ReactElement
30
+ */
31
+ export function HeaderField(headerFieldProps: HeaderFieldProps): ReactElement {
32
+ const { label, value, documentation, hidden = true } = headerFieldProps;
33
+ const [isCopyMessageBoxVisible, setMessageBoxVisibility] = useState(false);
34
+ const documentationContent = documentation && (
35
+ <PropertyDocumentation
36
+ defaultValue={documentation.defaultValue}
37
+ description={documentation.description}
38
+ propertyName={documentation.propertyName}
39
+ propertyType={documentation.propertyType}
40
+ />
41
+ );
42
+ const onCopy = useCallback(
43
+ () => (
44
+ <CopyButton
45
+ label={label}
46
+ onClick={(): void => {
47
+ copyToClipboard(value).catch((reason) => console.error(reason));
48
+ setMessageBoxVisibility(!isCopyMessageBoxVisible);
49
+ setTimeout(() => setMessageBoxVisibility(false), 3000);
50
+ }}
51
+ />
52
+ ),
53
+ []
54
+ );
55
+ return (
56
+ <>
57
+ <Stack horizontal={false} verticalAlign={'space-between'} style={{ marginBottom: 4 }}>
58
+ <UITooltip
59
+ hidden={hidden}
60
+ calloutProps={{ gapSpace: 5 }}
61
+ delay={2}
62
+ directionalHint={UIDirectionalHint.leftCenter}
63
+ tooltipProps={UITooltipUtils.renderContent(documentationContent ?? '')}>
64
+ <Label
65
+ htmlFor={label}
66
+ data-aria-label={label}
67
+ data-testid={`${label}--Label`}
68
+ style={{
69
+ color: 'var(--vscode-foreground)',
70
+ fontSize: sectionHeaderFontSize,
71
+ fontWeight: 'bold',
72
+ padding: 0,
73
+ width: '190px',
74
+ textOverflow: 'ellipsis',
75
+ whiteSpace: 'nowrap',
76
+ overflowX: 'hidden',
77
+ marginTop: 2,
78
+ marginBottom: 4
79
+ }}>
80
+ {label}
81
+ </Label>
82
+ </UITooltip>
83
+ <TextField
84
+ id={label}
85
+ value={value}
86
+ readOnly={true}
87
+ borderless
88
+ styles={{
89
+ field: {
90
+ color: 'var(--vscode-input-foreground)',
91
+ fontSize: defaultFontSize,
92
+ backgroundColor: 'var(--vscode-sideBar-background)'
93
+ },
94
+ fieldGroup: {
95
+ color: 'var(--vscode-input-foreground)',
96
+ backgroundColor: 'var(--vscode-sideBar-background)',
97
+ alignItems: 'center'
98
+ },
99
+ suffix: {
100
+ color: 'var(--vscode-input-foreground)',
101
+ backgroundColor: 'var(--vscode-sideBar-background)'
102
+ },
103
+ subComponentStyles: {
104
+ label: {
105
+ root: {
106
+ fontSize: sectionHeaderFontSize,
107
+ fontWeight: 'bold !important',
108
+ color: 'var(--vscode-foreground)'
109
+ }
110
+ }
111
+ }
112
+ }}
113
+ onRenderSuffix={onCopy}
114
+ />
115
+ {isCopyMessageBoxVisible && <Clipboard label={label} />}
116
+ </Stack>
117
+ </>
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Copy given string to clipboard, show toast success message.
123
+ *
124
+ * @param text {string to copy to clipboard}
125
+ */
126
+ async function copyToClipboard(text: string): Promise<void> {
127
+ await navigator.clipboard.writeText(text);
128
+ }
129
+ interface CopyButtonProps {
130
+ label: string;
131
+ onClick?: React.MouseEventHandler<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | HTMLSpanElement>;
132
+ }
133
+
134
+ /**
135
+ * Copy button component.
136
+ *
137
+ * @param props {CopyButtonProps}
138
+ * @returns ReactElement
139
+ */
140
+ function CopyButton(props: CopyButtonProps): ReactElement {
141
+ const { label, onClick } = props;
142
+
143
+ return (
144
+ <UIIconButton
145
+ id={`${label.replace(/\s/g, '')}--copy`}
146
+ iconProps={{ iconName: UiIcons.Copy }}
147
+ onClick={onClick}
148
+ />
149
+ );
150
+ }
@@ -0,0 +1,203 @@
1
+ import type { UIColumn } from '@sap-ux/ui-components';
2
+ import { UIDialog, UIIconButton, UiIcons, UISearchBox, UITable, SelectionMode } from '@sap-ux/ui-components';
3
+ import type { CSSProperties, ReactElement } from 'react';
4
+ import React, { useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import './SapUiIcon.scss';
8
+ import { changeProperty } from '../../slice';
9
+ import { setCachedValue } from './propertyValuesCache';
10
+ import { InputType } from './types';
11
+ import { useDispatch } from 'react-redux';
12
+
13
+ export interface IconValueHelpProps {
14
+ isIcon: boolean | undefined;
15
+ icons:
16
+ | {
17
+ name: string;
18
+ content: string;
19
+ fontFamily: string;
20
+ }[]
21
+ | [];
22
+ value: string;
23
+ controlId: string;
24
+ propertyName: string;
25
+ disabled: boolean;
26
+ }
27
+
28
+ /**
29
+ * React element for showing ui5 icon values.
30
+ *
31
+ * @param iconValueHelpProps IconValueHelpProps
32
+ * @returns ReactElement
33
+ */
34
+ export function IconValueHelp(iconValueHelpProps: IconValueHelpProps): ReactElement {
35
+ const { icons, value, propertyName, controlId, disabled } = iconValueHelpProps;
36
+ const dispatch = useDispatch();
37
+ const [newValue, setNewValue] = useState(value || '');
38
+ const { t } = useTranslation();
39
+ const [items, setItems] = useState(icons);
40
+ const [isDialogVisible, setDialogVisibility] = useState(false);
41
+
42
+ const onValueHelpButtonClick = (): void => {
43
+ setDialogVisibility(true);
44
+ };
45
+
46
+ const onSelectionChange = (rows: number[]): void => {
47
+ if (items && rows.length > 0) {
48
+ setNewValue('sap-icon://' + items[rows[0]].name);
49
+ }
50
+ };
51
+
52
+ const onFilterChange = (
53
+ event?: React.ChangeEvent<HTMLInputElement> | undefined,
54
+ filterValue?: string | undefined
55
+ ): void => {
56
+ if (filterValue && icons) {
57
+ setItems(
58
+ icons.filter((icon) => {
59
+ return icon.name.toLowerCase().includes(filterValue.toLowerCase());
60
+ })
61
+ );
62
+ } else {
63
+ setItems(icons);
64
+ }
65
+ };
66
+ const onIconColumnRender = (item: IconColumnProps) => (
67
+ <IconColumn content={item.content} fontFamily={item.fontFamily} />
68
+ );
69
+ const onLabelColumnRender = (item: LabelColumnProps) => (
70
+ <LabelColumn fontFamily={item.fontFamily} name={item.name} />
71
+ );
72
+
73
+ const col1: UIColumn = {
74
+ key: t('ICON'),
75
+ name: t('ICON'),
76
+ fieldName: t('ICON'),
77
+ minWidth: 68,
78
+ flexGrow: 1,
79
+ isResizable: true,
80
+ onRender: onIconColumnRender
81
+ };
82
+ const col2: UIColumn = {
83
+ key: t('ICON_NAME'),
84
+ name: t('ICON_NAME'),
85
+ fieldName: t('ICON_NAME'),
86
+ minWidth: 238,
87
+ flexGrow: 4,
88
+ isResizable: true,
89
+ onRender: onLabelColumnRender
90
+ };
91
+ const columns: UIColumn[] = [col1, col2];
92
+ const propertyNamePascalCase = propertyName[0].toUpperCase() + propertyName.substring(1);
93
+ return (
94
+ <>
95
+ <UIIconButton
96
+ id={`SelectIconFor${propertyNamePascalCase}`}
97
+ className="valueHelp-button"
98
+ title={t('SELECT_ICON')}
99
+ iconProps={{ iconName: UiIcons.ValueHelp }}
100
+ onClick={onValueHelpButtonClick}
101
+ disabled={disabled}
102
+ />
103
+ <UIDialog
104
+ hidden={!isDialogVisible}
105
+ modalProps={{
106
+ className: 'icon-dialog'
107
+ }}
108
+ dialogContentProps={{
109
+ title: t('SELECT_ICON')
110
+ }}
111
+ closeButtonAriaLabel={t('CLOSE')}
112
+ acceptButtonText={t('OK')}
113
+ cancelButtonText={t('CANCEL')}
114
+ onAccept={() => {
115
+ setDialogVisibility(false);
116
+ setItems(icons);
117
+ setCachedValue(controlId, propertyName, InputType.string, newValue);
118
+ const action = changeProperty({
119
+ controlId,
120
+ propertyName,
121
+ value: newValue
122
+ });
123
+ dispatch(action);
124
+ }}
125
+ onCancel={() => {
126
+ setDialogVisibility(false);
127
+ setItems(icons);
128
+ }}>
129
+ <div className="filter-icon-div" style={{ width: '100%', display: 'flex', alignItems: 'center' }}>
130
+ <UISearchBox
131
+ className="filter-icons"
132
+ autoFocus={false}
133
+ disableAnimation={false}
134
+ placeholder="Filter Icons"
135
+ onChange={onFilterChange}
136
+ />
137
+ </div>
138
+ <div className="icon-table-div" style={{ height: '100%', position: 'relative', marginTop: '10px' }}>
139
+ <UITable
140
+ className="icon-table space"
141
+ scrollablePaneProps={{
142
+ className: 'icon-table'
143
+ }}
144
+ selectionMode={SelectionMode.single}
145
+ dataSetKey={'datasetkey'}
146
+ items={items}
147
+ columns={columns}
148
+ onSelectionChange={onSelectionChange}
149
+ ariaLabelForSelectionColumn="Toggle selection"
150
+ ariaLabelForSelectAllCheckbox="Toggle selection for all items"
151
+ checkButtonAriaLabel="select row"
152
+ layoutMode={1}
153
+ isHeaderVisible={true}
154
+ />
155
+ </div>
156
+ </UIDialog>
157
+ </>
158
+ );
159
+ }
160
+ interface IconColumnProps {
161
+ fontFamily: string;
162
+ content: string;
163
+ }
164
+
165
+ /**
166
+ * React element for showing ui5 icon column.
167
+ *
168
+ * @param props IconColumnProps
169
+ * @returns ReactElement
170
+ */
171
+ function IconColumn(props: IconColumnProps): React.JSX.Element {
172
+ const { content, fontFamily } = props;
173
+ const style: CSSProperties = {
174
+ fontFamily: fontFamily,
175
+ fontSize: '1rem',
176
+ fontStyle: 'normal',
177
+ display: 'inline-block',
178
+ lineHeight: 0,
179
+ verticalAlign: 'baseLine',
180
+ textAlign: 'center',
181
+ marginTop: 8
182
+ };
183
+ return <span className="sapUiIcon icon-span" data-sap-ui-icon-content={content} style={style}></span>;
184
+ }
185
+ interface LabelColumnProps {
186
+ name: string;
187
+ fontFamily: string;
188
+ }
189
+ /**
190
+ * React element for showing ui5 label column.
191
+ *
192
+ * @param props { name: string; fontFamily: string }
193
+ * @returns ReactElement
194
+ */
195
+ function LabelColumn(props: LabelColumnProps): React.JSX.Element {
196
+ const { name, fontFamily } = props;
197
+ const style: CSSProperties = {
198
+ fontFamily: fontFamily,
199
+ fontSize: '13px',
200
+ fontStyle: 'normal'
201
+ };
202
+ return <span style={style}>{name}</span>;
203
+ }
@@ -0,0 +1,20 @@
1
+ import type { ReactElement } from 'react';
2
+ import React from 'react';
3
+ import { InputTypeToggle } from './InputTypeToggle';
4
+ import type { InputTypeWrapperProps } from './types';
5
+
6
+ /**
7
+ * Input type selector.
8
+ *
9
+ * @param props InputTypeWrapperProps
10
+ * @returns ReactElement
11
+ */
12
+ export function InputTypeSelector(props: InputTypeWrapperProps): ReactElement {
13
+ return (
14
+ <>
15
+ {props.toggleOptions.map((optionProps) => (
16
+ <InputTypeToggle key={optionProps.inputType} inputTypeProps={optionProps} {...props} />
17
+ ))}
18
+ </>
19
+ );
20
+ }
@@ -0,0 +1,4 @@
1
+ .selectedTypeButton {
2
+ background-color: var(--vscode-terminal-ansiBlue) !important;
3
+ outline: none !important;
4
+ }
@@ -0,0 +1,79 @@
1
+ import { UIIconButton } from '@sap-ux/ui-components';
2
+ import type { ReactElement } from 'react';
3
+ import React from 'react';
4
+ import { useDispatch } from 'react-redux';
5
+ import type { ControlProperty, StringControlPropertyWithOptions } from '@sap-ux-private/control-property-editor-common';
6
+ import { changeProperty } from '../../slice';
7
+ import type { CacheValue } from './propertyValuesCache';
8
+ import { getCachedValue } from './propertyValuesCache';
9
+ import styles from './InputTypeToggle.module.scss';
10
+ import type { InputTypeToggleProps } from './types';
11
+ import { InputType } from './types';
12
+ import { reportTelemetry, STRING_VALUE_TYPE } from '@sap-ux-private/control-property-editor-common';
13
+
14
+ /**
15
+ * Get value for input type.
16
+ *
17
+ * @param controlId string
18
+ * @param property ControlProperty
19
+ * @param inputType InputType
20
+ * @returns CacheValue
21
+ */
22
+ export function getValueForInputType(controlId: string, property: ControlProperty, inputType: InputType): CacheValue {
23
+ if (inputType === InputType.booleanTrue) {
24
+ return true;
25
+ } else if (inputType === InputType.booleanFalse) {
26
+ return false;
27
+ } else {
28
+ const cachedValue = getCachedValue(controlId, property.name, inputType);
29
+ if (cachedValue) {
30
+ return cachedValue;
31
+ } else if (inputType === InputType.expression) {
32
+ return '{expression}';
33
+ } else if (inputType === InputType.enumMember) {
34
+ return (property as StringControlPropertyWithOptions).options[0]?.key ?? '';
35
+ } else {
36
+ return property.type === STRING_VALUE_TYPE ? '' : 0;
37
+ }
38
+ }
39
+ }
40
+
41
+ /**
42
+ * React element for input type toggle.
43
+ *
44
+ * @param inputTypeToggleProps InputTypeToggleProps
45
+ * @returns ReactElement
46
+ */
47
+ export function InputTypeToggle(inputTypeToggleProps: InputTypeToggleProps): ReactElement {
48
+ const { inputTypeProps, controlId, property, controlName } = inputTypeToggleProps;
49
+ const { tooltip, iconName, selected } = inputTypeProps;
50
+ const dispatch = useDispatch();
51
+ return (
52
+ <UIIconButton
53
+ className={selected ? styles.selectedTypeButton : ''}
54
+ data-testid={`${property.name}--InputTypeToggle--${inputTypeProps.inputType}`}
55
+ iconProps={{ iconName: iconName }}
56
+ title={tooltip}
57
+ disabled={!property.isEnabled}
58
+ toggle={true}
59
+ checked={selected}
60
+ style={{ margin: 1, padding: 1 }}
61
+ onClick={(): void => {
62
+ if (inputTypeProps.selected) {
63
+ return;
64
+ } // click on already selected button should have no effect
65
+ const newValue = getValueForInputType(controlId, property, inputTypeProps.inputType);
66
+ reportTelemetry({ category: 'Property Change', propertyName: property.name }).catch((error) => {
67
+ console.error(`Error in reporting telemetry`, error);
68
+ });
69
+ const action = changeProperty({
70
+ controlId,
71
+ propertyName: property.name,
72
+ value: newValue,
73
+ controlName
74
+ });
75
+ dispatch(action);
76
+ }}
77
+ />
78
+ );
79
+ }