Skip to content

Commit 1f7c2eb

Browse files
Add typing support to steppers (#1209)
* add typing support to steppers * logic cleanup * misc cleanup * account for lack of unmount
1 parent 119dfa7 commit 1f7c2eb

File tree

4 files changed

+223
-33
lines changed

4 files changed

+223
-33
lines changed

Diff for: arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx

+28-33
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,15 @@ import {
2121
AsyncLocalizationProvider,
2222
LanguageInfo,
2323
} from '@theia/core/lib/common/i18n/localization';
24+
import SettingsStepInput from './settings-step-input';
2425

26+
const maxScale = 200;
27+
const minScale = -100;
28+
const scaleStep = 20;
29+
30+
const maxFontSize = 72;
31+
const minFontSize = 0;
32+
const fontSizeStep = 2;
2533
export class SettingsComponent extends React.Component<
2634
SettingsComponent.Props,
2735
SettingsComponent.State
@@ -88,6 +96,8 @@ export class SettingsComponent extends React.Component<
8896
}
8997

9098
protected renderSettings(): React.ReactNode {
99+
const scalePercentage = 100 + this.state.interfaceScale * 20;
100+
91101
return (
92102
<div className="content noselect">
93103
{nls.localize(
@@ -160,14 +170,13 @@ export class SettingsComponent extends React.Component<
160170
</div>
161171
<div className="column">
162172
<div className="flex-line">
163-
<input
164-
className="theia-input small"
165-
type="number"
166-
step={1}
167-
pattern="[0-9]+"
168-
onKeyDown={this.numbersOnlyKeyDown}
173+
<SettingsStepInput
169174
value={this.state.editorFontSize}
170-
onChange={this.editorFontSizeDidChange}
175+
setSettingsStateValue={this.setFontSize}
176+
step={fontSizeStep}
177+
maxValue={maxFontSize}
178+
minValue={minFontSize}
179+
classNames={{ input: 'theia-input small' }}
171180
/>
172181
</div>
173182
<div className="flex-line">
@@ -179,14 +188,13 @@ export class SettingsComponent extends React.Component<
179188
/>
180189
{nls.localize('arduino/preferences/automatic', 'Automatic')}
181190
</label>
182-
<input
183-
className="theia-input small with-margin"
184-
type="number"
185-
step={20}
186-
pattern="[0-9]+"
187-
onKeyDown={this.noopKeyDown}
188-
value={100 + this.state.interfaceScale * 20}
189-
onChange={this.interfaceScaleDidChange}
191+
<SettingsStepInput
192+
value={scalePercentage}
193+
setSettingsStateValue={this.setInterfaceScale}
194+
step={scaleStep}
195+
maxValue={maxScale}
196+
minValue={minScale}
197+
classNames={{ input: 'theia-input small with-margin' }}
190198
/>
191199
%
192200
</div>
@@ -516,13 +524,8 @@ export class SettingsComponent extends React.Component<
516524
}
517525
};
518526

519-
protected editorFontSizeDidChange = (
520-
event: React.ChangeEvent<HTMLInputElement>
521-
): void => {
522-
const { value } = event.target;
523-
if (value) {
524-
this.setState({ editorFontSize: parseInt(value, 10) });
525-
}
527+
private setFontSize = (editorFontSize: number) => {
528+
this.setState({ editorFontSize });
526529
};
527530

528531
protected rawAdditionalUrlsValueDidChange = (
@@ -539,18 +542,10 @@ export class SettingsComponent extends React.Component<
539542
this.setState({ autoScaleInterface: event.target.checked });
540543
};
541544

542-
protected interfaceScaleDidChange = (
543-
event: React.ChangeEvent<HTMLInputElement>
544-
): void => {
545-
const { value } = event.target;
546-
const percentage = parseInt(value, 10);
547-
if (isNaN(percentage)) {
548-
return;
549-
}
545+
private setInterfaceScale = (percentage: number) => {
550546
const interfaceScale = (percentage - 100) / 20;
551-
if (!isNaN(interfaceScale)) {
552-
this.setState({ interfaceScale });
553-
}
547+
548+
this.setState({ interfaceScale });
554549
};
555550

556551
protected verifyAfterUploadDidChange = (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import * as React from '@theia/core/shared/react';
2+
import classnames from 'classnames';
3+
4+
interface SettingsStepInputProps {
5+
value: number;
6+
setSettingsStateValue: (value: number) => void;
7+
step: number;
8+
maxValue: number;
9+
minValue: number;
10+
classNames?: { [key: string]: string };
11+
}
12+
13+
const SettingsStepInput: React.FC<SettingsStepInputProps> = (
14+
props: SettingsStepInputProps
15+
) => {
16+
const { value, setSettingsStateValue, step, maxValue, minValue, classNames } =
17+
props;
18+
19+
const [stepUpDisabled, setStepUpDisabled] = React.useState(false);
20+
const [stepDownDisabled, setStepDownDisabled] = React.useState(false);
21+
22+
const onStepUp = (): void => {
23+
const valueRoundedToScale = Math.ceil(value / step) * step;
24+
const calculatedValue =
25+
valueRoundedToScale === value ? value + step : valueRoundedToScale;
26+
const newValue = limitValueByCondition(
27+
calculatedValue,
28+
maxValue,
29+
calculatedValue >= maxValue,
30+
disableStepUp
31+
);
32+
33+
setSettingsStateValue(newValue);
34+
};
35+
36+
const onStepDown = (): void => {
37+
const valueRoundedToScale = Math.floor(value / step) * step;
38+
const calculatedValue =
39+
valueRoundedToScale === value ? value - step : valueRoundedToScale;
40+
const newValue = limitValueByCondition(
41+
calculatedValue,
42+
minValue,
43+
calculatedValue <= minValue,
44+
disableStepDown
45+
);
46+
47+
setSettingsStateValue(newValue);
48+
};
49+
50+
const limitValueByCondition = (
51+
calculatedValue: number,
52+
limitedValue: number,
53+
condition: boolean,
54+
onConditionTrue: () => void,
55+
onConditionFalse = enableButtons
56+
): number => {
57+
if (condition) {
58+
onConditionTrue();
59+
return limitedValue;
60+
} else {
61+
onConditionFalse();
62+
return calculatedValue;
63+
}
64+
};
65+
66+
const enableButtons = (): void => {
67+
setStepUpDisabled(false);
68+
setStepDownDisabled(false);
69+
};
70+
71+
const disableStepUp = (): void => {
72+
setStepUpDisabled(true);
73+
};
74+
75+
const disableStepDown = (): void => {
76+
setStepDownDisabled(true);
77+
};
78+
79+
const onUserInput = (event: React.ChangeEvent<HTMLInputElement>): void => {
80+
const { value: eventValue } = event.target;
81+
82+
if (eventValue === '') {
83+
setSettingsStateValue(0);
84+
}
85+
86+
const number = Number(eventValue);
87+
88+
if (!isNaN(number) && number !== value) {
89+
let newValue;
90+
if (number > value) {
91+
newValue = limitValueByCondition(
92+
number,
93+
maxValue,
94+
number >= maxValue,
95+
disableStepUp
96+
);
97+
} else {
98+
newValue = limitValueByCondition(
99+
number,
100+
minValue,
101+
number <= minValue,
102+
disableStepDown
103+
);
104+
}
105+
106+
setSettingsStateValue(newValue);
107+
}
108+
};
109+
110+
// the component does not unmount when we close the settings dialog
111+
// in theia which necessitates the below useEffect
112+
React.useEffect(() => {
113+
if (value > minValue && value < maxValue) {
114+
enableButtons();
115+
}
116+
}, [value, minValue, maxValue]);
117+
118+
return (
119+
<div className="settings-step-input-container">
120+
<input
121+
className={classnames('settings-step-input-element', classNames?.input)}
122+
value={value.toString()}
123+
onChange={onUserInput}
124+
type="number"
125+
pattern="[0-9]+"
126+
/>
127+
<div className="settings-step-input-buttons-container">
128+
<button
129+
className="settings-step-input-button settings-step-input-up-button"
130+
disabled={stepUpDisabled}
131+
onClick={onStepUp}
132+
>
133+
&#9662;
134+
</button>
135+
<button
136+
className="settings-step-input-button"
137+
disabled={stepDownDisabled}
138+
onClick={onStepDown}
139+
>
140+
&#9662;
141+
</button>
142+
</div>
143+
</div>
144+
);
145+
};
146+
147+
export default SettingsStepInput;

Diff for: arduino-ide-extension/src/browser/style/index.css

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
@import './fonts.css';
1919
@import './custom-codicon.css';
2020
@import './progress-bar.css';
21+
@import './settings-step-input.css';
2122

2223
.theia-input.warning:focus {
2324
outline-width: 1px;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
.settings-step-input-container {
2+
position: relative
3+
}
4+
5+
.settings-step-input-element::-webkit-inner-spin-button,
6+
.settings-step-input-element::-webkit-outer-spin-button {
7+
-webkit-appearance: none;
8+
margin: 0;
9+
}
10+
11+
.settings-step-input-buttons-container {
12+
display: none;
13+
flex-direction: column;
14+
position: absolute;
15+
right: 0px;
16+
top: 50%;
17+
transform: translate(0px, -50%);
18+
height: calc(100% - 4px);
19+
width: 14px;
20+
padding: 2px;
21+
background: white;
22+
}
23+
24+
.settings-step-input-container:hover > .settings-step-input-buttons-container {
25+
display: flex;
26+
}
27+
28+
.settings-step-input-up-button {
29+
transform: rotate(-180deg);
30+
}
31+
32+
.settings-step-input-button {
33+
border: none;
34+
border-radius: 0;
35+
height: 50%;
36+
width: 1;
37+
display: flex;
38+
align-items: center;
39+
justify-content: center;
40+
user-select: none;
41+
cursor: pointer;
42+
line-height: 12px;
43+
}
44+
45+
.settings-step-input-button:hover {
46+
background: rgba(128, 128, 128, 0.8);
47+
}

0 commit comments

Comments
 (0)