mirror of
https://github.com/portainer/portainer.git
synced 2025-07-23 07:19:41 +02:00
fix(slider): use and update react slider EE-4522 (#7987)
This commit is contained in:
parent
f94147b07b
commit
9f3d5185b0
10 changed files with 154 additions and 43 deletions
|
@ -93,17 +93,18 @@
|
||||||
<div class="form-group flex flex-row !mb-0">
|
<div class="form-group flex flex-row !mb-0">
|
||||||
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left"> Memory limit (MB) </label>
|
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left"> Memory limit (MB) </label>
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<slider
|
<por-slider
|
||||||
model="$ctrl.formValues.MemoryLimit"
|
min="$ctrl.defaults.MemoryLimit"
|
||||||
floor="$ctrl.defaults.MemoryLimit"
|
max="$ctrl.state.sliderMaxMemory"
|
||||||
ceil="$ctrl.state.sliderMaxMemory"
|
|
||||||
step="128"
|
step="128"
|
||||||
ng-if="$ctrl.state.sliderMaxMemory"
|
ng-if="$ctrl.state.sliderMaxMemory"
|
||||||
|
value="$ctrl.formValues.MemoryLimit"
|
||||||
|
on-change="($ctrl.handleMemoryLimitChange)"
|
||||||
|
visible-tooltip="true"
|
||||||
data-cy="k8sNamespaceCreate-memoryLimitSlider"
|
data-cy="k8sNamespaceCreate-memoryLimitSlider"
|
||||||
>
|
></por-slider>
|
||||||
</slider>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2 vertical-center">
|
<div class="col-sm-2 vertical-center pt-6">
|
||||||
<input
|
<input
|
||||||
name="memory_limit"
|
name="memory_limit"
|
||||||
type="number"
|
type="number"
|
||||||
|
@ -138,16 +139,16 @@
|
||||||
<div class="form-group flex flex-row">
|
<div class="form-group flex flex-row">
|
||||||
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left"> CPU limit </label>
|
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left"> CPU limit </label>
|
||||||
<div class="col-xs-8">
|
<div class="col-xs-8">
|
||||||
<slider
|
<por-slider
|
||||||
model="$ctrl.formValues.CpuLimit"
|
min="$ctrl.defaults.CpuLimit"
|
||||||
floor="$ctrl.defaults.CpuLimit"
|
max="$ctrl.state.sliderMaxCpu"
|
||||||
ceil="$ctrl.state.sliderMaxCpu"
|
|
||||||
step="0.1"
|
step="0.1"
|
||||||
precision="2"
|
|
||||||
ng-if="$ctrl.state.sliderMaxCpu"
|
ng-if="$ctrl.state.sliderMaxCpu"
|
||||||
|
value="$ctrl.formValues.CpuLimit"
|
||||||
|
on-change="($ctrl.handleCpuLimitChange)"
|
||||||
data-cy="k8sNamespaceCreate-cpuLimitSlider"
|
data-cy="k8sNamespaceCreate-cpuLimitSlider"
|
||||||
>
|
visible-tooltip="true"
|
||||||
</slider>
|
></por-slider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !cpu-limit-input -->
|
<!-- !cpu-limit-input -->
|
||||||
|
|
|
@ -33,6 +33,8 @@ class KubernetesCreateResourcePoolController {
|
||||||
this.onToggleResourceQuota = this.onToggleResourceQuota.bind(this);
|
this.onToggleResourceQuota = this.onToggleResourceQuota.bind(this);
|
||||||
this.onChangeIngressControllerAvailability = this.onChangeIngressControllerAvailability.bind(this);
|
this.onChangeIngressControllerAvailability = this.onChangeIngressControllerAvailability.bind(this);
|
||||||
this.onRegistriesChange = this.onRegistriesChange.bind(this);
|
this.onRegistriesChange = this.onRegistriesChange.bind(this);
|
||||||
|
this.handleMemoryLimitChange = this.handleMemoryLimitChange.bind(this);
|
||||||
|
this.handleCpuLimitChange = this.handleCpuLimitChange.bind(this);
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
|
@ -101,6 +103,18 @@ class KubernetesCreateResourcePoolController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMemoryLimitChange(memoryLimit) {
|
||||||
|
return this.$async(async () => {
|
||||||
|
this.formValues.MemoryLimit = memoryLimit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCpuLimitChange(cpuLimit) {
|
||||||
|
return this.$async(async () => {
|
||||||
|
this.formValues.CpuLimit = cpuLimit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/* #region CREATE NAMESPACE */
|
/* #region CREATE NAMESPACE */
|
||||||
createResourcePool() {
|
createResourcePool() {
|
||||||
return this.$async(async () => {
|
return this.$async(async () => {
|
||||||
|
|
|
@ -80,15 +80,18 @@
|
||||||
<div class="form-group flex">
|
<div class="form-group flex">
|
||||||
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left vertical-center"> Memory limit (MB) </label>
|
<label for="memory-limit" class="col-sm-3 col-lg-2 control-label text-left vertical-center"> Memory limit (MB) </label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<slider
|
<por-slider
|
||||||
model="ctrl.formValues.MemoryLimit"
|
min="ctrl.ResourceQuotaDefaults.MemoryLimit"
|
||||||
floor="ctrl.ResourceQuotaDefaults.MemoryLimit"
|
max="ctrl.state.sliderMaxMemory"
|
||||||
ceil="ctrl.state.sliderMaxMemory"
|
|
||||||
step="128"
|
step="128"
|
||||||
ng-if="ctrl.state.sliderMaxMemory"
|
ng-if="ctrl.state.sliderMaxMemory"
|
||||||
></slider>
|
value="ctrl.formValues.MemoryLimit"
|
||||||
|
on-change="(ctrl.handleMemoryLimitChange)"
|
||||||
|
visible-tooltip="true"
|
||||||
|
data-cy="k8sNamespaceEdit-memoryLimitSlider"
|
||||||
|
></por-slider>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2 vertical-center">
|
<div class="col-sm-2 vertical-center pt-6">
|
||||||
<input
|
<input
|
||||||
name="memory_limit"
|
name="memory_limit"
|
||||||
type="number"
|
type="number"
|
||||||
|
@ -97,6 +100,7 @@
|
||||||
class="form-control"
|
class="form-control"
|
||||||
ng-model="ctrl.formValues.MemoryLimit"
|
ng-model="ctrl.formValues.MemoryLimit"
|
||||||
id="memory-limit"
|
id="memory-limit"
|
||||||
|
data-cy="k8sNamespaceEdit-memoryLimitInput"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -117,14 +121,16 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px"> CPU limit </label>
|
<label for="cpu-limit" class="col-sm-3 col-lg-2 control-label text-left" style="margin-top: 20px"> CPU limit </label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<slider
|
<por-slider
|
||||||
model="ctrl.formValues.CpuLimit"
|
min="ctrl.ResourceQuotaDefaults.CpuLimit"
|
||||||
floor="ctrl.ResourceQuotaDefaults.CpuLimit"
|
max="ctrl.state.sliderMaxCpu"
|
||||||
ceil="ctrl.state.sliderMaxCpu"
|
|
||||||
step="0.1"
|
step="0.1"
|
||||||
precision="2"
|
|
||||||
ng-if="ctrl.state.sliderMaxCpu"
|
ng-if="ctrl.state.sliderMaxCpu"
|
||||||
></slider>
|
value="ctrl.formValues.CpuLimit"
|
||||||
|
on-change="(ctrl.handleCpuLimitChange)"
|
||||||
|
data-cy="k8sNamespaceEdit-cpuLimitSlider"
|
||||||
|
visible-tooltip="true"
|
||||||
|
></por-slider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !cpu-limit-input -->
|
<!-- !cpu-limit-input -->
|
||||||
|
|
|
@ -69,6 +69,8 @@ class KubernetesResourcePoolController {
|
||||||
this.onToggleStorageQuota = this.onToggleStorageQuota.bind(this);
|
this.onToggleStorageQuota = this.onToggleStorageQuota.bind(this);
|
||||||
this.onChangeIngressControllerAvailability = this.onChangeIngressControllerAvailability.bind(this);
|
this.onChangeIngressControllerAvailability = this.onChangeIngressControllerAvailability.bind(this);
|
||||||
this.onRegistriesChange = this.onRegistriesChange.bind(this);
|
this.onRegistriesChange = this.onRegistriesChange.bind(this);
|
||||||
|
this.handleMemoryLimitChange = this.handleMemoryLimitChange.bind(this);
|
||||||
|
this.handleCpuLimitChange = this.handleCpuLimitChange.bind(this);
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
|
@ -122,6 +124,18 @@ class KubernetesResourcePoolController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMemoryLimitChange(memoryLimit) {
|
||||||
|
return this.$async(async () => {
|
||||||
|
this.formValues.MemoryLimit = memoryLimit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCpuLimitChange(cpuLimit) {
|
||||||
|
return this.$async(async () => {
|
||||||
|
this.formValues.CpuLimit = cpuLimit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
showEditor() {
|
showEditor() {
|
||||||
this.state.showEditorTab = true;
|
this.state.showEditorTab = true;
|
||||||
this.selectTab(2);
|
this.selectTab(2);
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { FallbackImage } from '@@/FallbackImage';
|
||||||
import { BadgeIcon } from '@@/BadgeIcon';
|
import { BadgeIcon } from '@@/BadgeIcon';
|
||||||
import { TeamsSelector } from '@@/TeamsSelector';
|
import { TeamsSelector } from '@@/TeamsSelector';
|
||||||
import { PortainerSelect } from '@@/form-components/PortainerSelect';
|
import { PortainerSelect } from '@@/form-components/PortainerSelect';
|
||||||
|
import { Slider } from '@@/form-components/Slider';
|
||||||
|
|
||||||
import { fileUploadField } from './file-upload-field';
|
import { fileUploadField } from './file-upload-field';
|
||||||
import { switchField } from './switch-field';
|
import { switchField } from './switch-field';
|
||||||
|
@ -184,6 +185,18 @@ export const componentsModule = angular
|
||||||
'isClearable',
|
'isClearable',
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
.component(
|
||||||
|
'porSlider',
|
||||||
|
r2a(Slider, [
|
||||||
|
'min',
|
||||||
|
'max',
|
||||||
|
'step',
|
||||||
|
'value',
|
||||||
|
'onChange',
|
||||||
|
'visibleTooltip',
|
||||||
|
'dataCy',
|
||||||
|
])
|
||||||
|
)
|
||||||
.component(
|
.component(
|
||||||
'porAccessManagementUsersSelector',
|
'porAccessManagementUsersSelector',
|
||||||
r2a(PorAccessManagementUsersSelector, ['onChange', 'options', 'value'])
|
r2a(PorAccessManagementUsersSelector, ['onChange', 'options', 'value'])
|
||||||
|
|
|
@ -7,26 +7,50 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider :global .rc-slider-handle {
|
.slider :global .rc-slider-handle {
|
||||||
width: 32px;
|
@apply border-blue-8 border-2;
|
||||||
height: 32px;
|
width: 24px;
|
||||||
margin-top: -14px;
|
height: 24px;
|
||||||
|
margin-top: -8px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #0db9f0;
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider :global .rc-slider-track {
|
||||||
|
@apply bg-blue-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider :global .rc-slider-handle:after {
|
.slider :global .rc-slider-handle:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 8px;
|
||||||
left: 10px;
|
left: 8px;
|
||||||
width: 8px;
|
width: 9px;
|
||||||
height: 8px;
|
height: 9px;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 4px;
|
border-radius: 5px;
|
||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider :global .rc-slider-mark-text,
|
.slider :global .rc-slider-mark-text {
|
||||||
.slider :global .rc-slider-tooltip-inner {
|
font-size: 14px;
|
||||||
font-family: Inter, serif;
|
color: var(--text-body-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider :global .rc-slider-tooltip-arrow {
|
||||||
|
bottom: 2px;
|
||||||
|
border-top-color: var(--bg-tooltip-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider :global .rc-slider-tooltip-placement-top {
|
||||||
|
padding: 6px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider :global .rc-slider-tooltip-inner {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-tooltip-color);
|
||||||
|
height: fit-content;
|
||||||
|
background-color: var(--bg-tooltip-color);
|
||||||
|
box-shadow: 0 2px 4px 0 rgb(34 36 38 / 12%), 0 2px 10px 0 rgb(34 36 38 / 15%);
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,14 @@ export default {
|
||||||
title: 'Components/Form/Slider',
|
title: 'Components/Form/Slider',
|
||||||
} as Meta;
|
} as Meta;
|
||||||
|
|
||||||
function Template({ value, min, max, step }: JSX.IntrinsicAttributes & Props) {
|
function Template({
|
||||||
|
value,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
step,
|
||||||
|
dataCy,
|
||||||
|
visibleTooltip,
|
||||||
|
}: JSX.IntrinsicAttributes & Props) {
|
||||||
const [sliderValue, setSliderValue] = useState(min);
|
const [sliderValue, setSliderValue] = useState(min);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -22,6 +29,8 @@ function Template({ value, min, max, step }: JSX.IntrinsicAttributes & Props) {
|
||||||
step={step}
|
step={step}
|
||||||
value={sliderValue}
|
value={sliderValue}
|
||||||
onChange={setSliderValue}
|
onChange={setSliderValue}
|
||||||
|
dataCy={dataCy}
|
||||||
|
visibleTooltip={visibleTooltip}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -32,4 +41,6 @@ Primary.args = {
|
||||||
max: 100,
|
max: 100,
|
||||||
step: 1,
|
step: 1,
|
||||||
value: 5,
|
value: 5,
|
||||||
|
visibleTooltip: true,
|
||||||
|
dataCy: 'someView-coolSlider',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,9 +8,19 @@ function renderDefault({
|
||||||
step = 1,
|
step = 1,
|
||||||
value = min,
|
value = min,
|
||||||
onChange = () => {},
|
onChange = () => {},
|
||||||
|
dataCy = 'someView-coolSlider',
|
||||||
|
visibleTooltip = true,
|
||||||
}: Partial<Props> = {}) {
|
}: Partial<Props> = {}) {
|
||||||
return render(
|
return render(
|
||||||
<Slider min={min} max={max} step={step} onChange={onChange} value={value} />
|
<Slider
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
step={step}
|
||||||
|
onChange={onChange}
|
||||||
|
value={value}
|
||||||
|
visibleTooltip={visibleTooltip}
|
||||||
|
dataCy={dataCy}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,25 @@ export interface Props {
|
||||||
step: number;
|
step: number;
|
||||||
value: number;
|
value: number;
|
||||||
onChange: (value: number) => void;
|
onChange: (value: number) => void;
|
||||||
|
// true if you want to always show the tooltip
|
||||||
|
dataCy: string;
|
||||||
|
visibleTooltip?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Slider({ min, max, step, value, onChange }: Props) {
|
export function Slider({
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
step,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
dataCy,
|
||||||
|
visibleTooltip: visible,
|
||||||
|
}: Props) {
|
||||||
const SliderWithTooltip = RcSlider.createSliderWithTooltip(RcSlider);
|
const SliderWithTooltip = RcSlider.createSliderWithTooltip(RcSlider);
|
||||||
|
// if the tooltip is always visible, hide the marks when tooltip value gets close to the edges
|
||||||
const marks = {
|
const marks = {
|
||||||
[min]: translateMinValue(min),
|
[min]: visible && value / max < 0.1 ? '' : translateMinValue(min),
|
||||||
[max]: max.toString(),
|
[max]: visible && value / max > 0.9 ? '' : max.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -29,6 +41,11 @@ export function Slider({ min, max, step, value, onChange }: Props) {
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
onAfterChange={onChange}
|
onAfterChange={onChange}
|
||||||
className={styles.slider}
|
className={styles.slider}
|
||||||
|
tipProps={{ visible }}
|
||||||
|
railStyle={{ height: 8 }}
|
||||||
|
trackStyle={{ height: 8 }}
|
||||||
|
dotStyle={{ visibility: 'hidden' }}
|
||||||
|
data-cy={dataCy}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
1
app/react/components/form-components/Slider/index.ts
Normal file
1
app/react/components/form-components/Slider/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { Slider } from './Slider';
|
Loading…
Add table
Add a link
Reference in a new issue