Initial commit from prod-batam

This commit is contained in:
mario
2025-05-27 10:51:12 +07:00
commit 025b96229b
3361 changed files with 304068 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
.input-range-thumb-design {
@apply bg-primary-light border-primary-dark border-[2px] border-solid;
border-radius: 50%;
}

View File

@@ -0,0 +1,238 @@
import React, { useState, useEffect, useRef } from 'react';
import classNames from 'classnames';
import { InputNumber } from '../../components'; // Import InputNumber component
import './InputDoubleRange.css';
type InputDoubleRangeProps = {
values: [number, number];
onChange: (values: [number, number]) => void;
minValue?: number;
maxValue?: number;
step?: number;
unit?: string;
containerClassName?: string;
inputClassName?: string;
labelClassName?: string;
labelVariant?: string;
showLabel?: boolean;
labelPosition?: 'left' | 'right';
trackColor?: string;
allowNumberEdit?: boolean;
showAdjustmentArrows?: boolean;
allowOutOfRange?: boolean;
};
const InputDoubleRange: React.FC<InputDoubleRangeProps> = ({
values,
onChange,
minValue = 0,
maxValue = 100,
step = 1,
unit = '',
containerClassName = '',
inputClassName = '',
labelClassName = '',
labelVariant = 'body1',
showLabel = false,
labelPosition = 'left',
trackColor = 'primary',
allowNumberEdit = false,
allowOutOfRange = false,
showAdjustmentArrows = false,
}) => {
// Set initial thumb positions as percentages
const initialPercentageStart = Math.round(((values[0] - minValue) / (maxValue - minValue)) * 100);
const initialPercentageEnd = Math.round(((values[1] - minValue) / (maxValue - minValue)) * 100);
const [percentageStart, setPercentageStart] = useState(initialPercentageStart);
const [percentageEnd, setPercentageEnd] = useState(initialPercentageEnd);
const [rangeValue, setRangeValue] = useState(values);
const selectedThumbRef = useRef(null);
const sliderRef = useRef(null);
const updateRangeValues = (newValues, index = null) => {
const updatedRangeValue = Array.isArray(newValues) ? [...newValues] : [...rangeValue];
if (index !== null) {
updatedRangeValue[index] = newValues;
}
const calculatePercentage = value => {
if (value < minValue) {
return 0;
}
if (value > maxValue) {
return 100;
}
return ((value - minValue) / (maxValue - minValue)) * 100;
};
const newPercentageStart = calculatePercentage(updatedRangeValue[0]);
const newPercentageEnd = calculatePercentage(updatedRangeValue[1]);
setRangeValue(updatedRangeValue);
onChange(updatedRangeValue);
setPercentageStart(newPercentageStart);
setPercentageEnd(newPercentageEnd);
};
useEffect(() => {
updateRangeValues(values);
}, [values, minValue, maxValue]);
const LabelOrEditableNumber = (val, index) => {
return allowNumberEdit ? (
// the pl-[2px] class is used to align the thumb so that it doesn't
// go over the label when the value is full, not sure what is wrong
// with the implementation, we need to fix it properly
<div className={index === 1 && 'pl-[2px]'}>
<InputNumber
minValue={minValue}
maxValue={maxValue}
value={val}
onChange={newValue => {
updateRangeValues(newValue, index);
}}
step={step}
labelClassName={classNames(labelClassName ?? 'text-white')}
showAdjustmentArrows={showAdjustmentArrows}
/>
</div>
) : (
<span className={classNames(labelClassName ?? 'text-white')}>
{val}
{unit}
</span>
);
};
useEffect(() => {
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleGlobalMouseUp);
};
}, []);
const handleGlobalMouseUp = () => {
// Remove global mouse event listeners
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleGlobalMouseUp);
selectedThumbRef.current = null;
};
const handleMouseDown = e => {
const rect = sliderRef.current.getBoundingClientRect();
const x = e.clientX - rect.left;
const percentageClicked = (x / rect.width) * 100;
// Calculate the distances from the clicked point to both thumbs' positions
const distanceToStartThumb = Math.abs(percentageClicked - percentageStart);
const distanceToEndThumb = Math.abs(percentageClicked - percentageEnd);
// Check if the clicked point is within a threshold distance to either thumb
if (distanceToStartThumb < 10) {
selectedThumbRef.current = 0;
} else if (distanceToEndThumb < 10) {
selectedThumbRef.current = 1;
}
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleGlobalMouseUp);
};
const handleMouseMove = e => {
const selectedThumbValue = selectedThumbRef.current;
if (selectedThumbValue === null) {
return;
}
const rect = sliderRef.current.getBoundingClientRect();
const x = e.clientX - rect.left;
const newValue =
Math.round(((x / rect.width) * (maxValue - minValue) + minValue) / step) * step;
if (!allowOutOfRange) {
const clampedValue = Math.min(Math.max(newValue, minValue), maxValue);
const updatedRangeValue = [...rangeValue];
updatedRangeValue[selectedThumbValue] = clampedValue;
setRangeValue(updatedRangeValue);
onChange(updatedRangeValue);
const percentage = Math.round(((clampedValue - minValue) / (maxValue - minValue)) * 100);
if (selectedThumbValue === 0) {
setPercentageStart(percentage);
} else {
setPercentageEnd(percentage);
}
} else {
const updatedRangeValue = [...rangeValue];
updatedRangeValue[selectedThumbValue] = newValue;
setRangeValue(updatedRangeValue);
onChange(updatedRangeValue);
// Update the thumb position
const percentage = Math.round(((newValue - minValue) / (maxValue - minValue)) * 100);
if (percentage < 0) {
if (selectedThumbValue === 0) {
setPercentageStart(0);
} else {
setPercentageEnd(0);
}
} else if (percentage > 100) {
if (selectedThumbValue === 0) {
setPercentageStart(100);
} else {
setPercentageEnd(100);
}
} else {
if (selectedThumbValue === 0) {
setPercentageStart(percentage);
} else {
setPercentageEnd(percentage);
}
}
}
// Update the correct values in the rangeValue array
};
// Calculate the range values percentages for gradient background
const rangeValuePercentageStart = ((rangeValue[0] - minValue) / (maxValue - minValue)) * 100;
const rangeValuePercentageEnd = ((rangeValue[1] - minValue) / (maxValue - minValue)) * 100;
return (
<div className={`flex select-none items-center space-x-2 ${containerClassName ?? ''}`}>
{showLabel && LabelOrEditableNumber(rangeValue[0], 0)}
<div
className="relative flex h-10 w-full items-center"
onMouseDown={handleMouseDown}
ref={sliderRef}
>
<div
className="h-[3px] w-full rounded-lg"
style={{
background: `linear-gradient(to right, #3a3f99 0%, #3a3f99 ${rangeValuePercentageStart}%, #5acce6 ${rangeValuePercentageStart}%, #5acce6 ${rangeValuePercentageEnd}%, #3a3f99 ${rangeValuePercentageEnd}%, #3a3f99 100%)`,
}}
></div>
<div
className="input-range-thumb-design absolute h-3 w-3 cursor-pointer"
style={{
left: `calc(${percentageStart}% - 3px)`,
}}
></div>
<div
className="input-range-thumb-design absolute h-3 w-3 cursor-pointer rounded-full"
style={{ left: `calc(${percentageEnd}% - 3px)` }}
></div>
</div>
{showLabel && LabelOrEditableNumber(rangeValue[1], 1)}
</div>
);
};
export default InputDoubleRange;

View File

@@ -0,0 +1,56 @@
import { InputDoubleRange } from '../../../components';
import { ArgsTable, Story, Canvas, Meta } from '@storybook/addon-docs';
export const argTypes = {
component: InputDoubleRange,
title: 'Components/InputDoubleRange',
};
<Meta
title="Components/InputDoubleRange"
component={InputDoubleRange}
/>
export const RangeInputTemplate = args => (
<div className="w-12">
<InputDoubleRange {...args} />
</div>
);
<Heading
title="InputDoubleRange"
componentRelativePath="InputDoubleRange/InputDoubleRange.tsx"
/>
- [Overview](#overview)
- [Props](#props)
- [Contribute](#contribute)
## Overview
InputDoubleRange is a component that allows you to use as a boolean value
<Canvas>
<Story
name="Overview"
args={{
values: [100, 200],
minValue: 0,
maxValue: 300,
step: 10,
unit: '%',
onChange: () => console.log('input range change'),
labelClassName: 'text-black',
}}
>
{RangeInputTemplate.bind({})}
</Story>
</Canvas>
## Props
<ArgsTable of={InputDoubleRange} />
## Contribute
<Footer componentRelativePath="InputDoubleRange/__stories__/InputDoubleRange.stories.mdx" />

View File

@@ -0,0 +1,3 @@
import InputDoubleRange from './InputDoubleRange';
export default InputDoubleRange;