diff --git a/src/components/middle/message/WebPageStarGiftAuction.module.scss b/src/components/middle/message/WebPageStarGiftAuction.module.scss
index 11c69ceed..8192bd19d 100644
--- a/src/components/middle/message/WebPageStarGiftAuction.module.scss
+++ b/src/components/middle/message/WebPageStarGiftAuction.module.scss
@@ -58,12 +58,15 @@
top: 0.5rem;
left: 0.5rem;
- padding: 0.125rem 0.375rem;
+ display: flex;
+ align-items: center;
+
+ height: 1rem;
+ padding: 0 0.375rem;
border-radius: 1rem;
font-size: 0.6875rem;
font-weight: var(--font-weight-medium);
- line-height: 0.75rem;
backdrop-filter: blur(0.5rem);
}
diff --git a/src/components/modals/gift/auction/GiftAuctionBidModal.module.scss b/src/components/modals/gift/auction/GiftAuctionBidModal.module.scss
index 7c6f53a12..c13a65893 100644
--- a/src/components/modals/gift/auction/GiftAuctionBidModal.module.scss
+++ b/src/components/modals/gift/auction/GiftAuctionBidModal.module.scss
@@ -88,6 +88,7 @@
}
.winningBadge {
+ margin-top: 0.125rem;
padding: 0.0625rem 0.5rem;
border-radius: 0.875rem;
diff --git a/src/components/modals/gift/auction/GiftAuctionBidModal.tsx b/src/components/modals/gift/auction/GiftAuctionBidModal.tsx
index 6dbb6f859..9d8d998df 100644
--- a/src/components/modals/gift/auction/GiftAuctionBidModal.tsx
+++ b/src/components/modals/gift/auction/GiftAuctionBidModal.tsx
@@ -100,14 +100,6 @@ const GiftAuctionBidModal = ({
const sliderMaxValue = Math.ceil(currentMinBid / BID_ROUNDING_STEP) * BID_ROUNDING_STEP + MAX_BID_AMOUNT_STEP;
- const currentProgress = (currentMinBid - baseMinBid) / (sliderMaxValue - baseMinBid);
- const adjustedMinBid = Math.floor(
- (currentMinBid - MIN_SLIDER_PROGRESS * sliderMaxValue) / (1 - MIN_SLIDER_PROGRESS),
- );
- const giftMinBid = currentProgress > MIN_SLIDER_PROGRESS
- ? Math.max(1, adjustedMinBid)
- : baseMinBid;
-
useEffect(() => {
setSelectedBidAmount(currentMinBid);
}, [currentMinBid]);
@@ -137,9 +129,13 @@ const GiftAuctionBidModal = ({
loadActiveGiftAuction({ giftId: renderingAuctionState.gift.id });
});
+ const handleRequestCustomValue = useLastCallback(() => {
+ openCustomBidModal();
+ });
+
const handleBadgeClick = useLastCallback(() => {
if (isAtMaxValue) {
- openCustomBidModal();
+ handleRequestCustomValue();
}
});
@@ -354,12 +350,14 @@ const GiftAuctionBidModal = ({
diff --git a/src/components/modals/paidReaction/StarSlider.tsx b/src/components/modals/paidReaction/StarSlider.tsx
index ab84513b6..5c40dcfc8 100644
--- a/src/components/modals/paidReaction/StarSlider.tsx
+++ b/src/components/modals/paidReaction/StarSlider.tsx
@@ -23,12 +23,14 @@ type OwnProps = {
defaultValue: number;
minValue?: number;
minAllowedValue?: number;
+ minAllowedProgress?: number;
className?: string;
floatingBadgeDescription?: TeactNode;
shouldUseDynamicColor?: boolean;
shouldAllowCustomValue?: boolean;
onChange: (value: number) => void;
onBadgeClick?: NoneToVoidFunction;
+ onCustomValueClick?: NoneToVoidFunction;
};
const DEFAULT_POINTS = [50, 100, 500, 1000, 2000, 5000, 10000];
@@ -65,62 +67,63 @@ const SLIDER_COLORS = [
'#40A920', // Green
'#E29A09', // Yellow
'#ED771E', // Orange
- '#E14542', // Red
- '#596473', // Silver (100% only)
+ '#E14741', // Red
+ '#5B6676', // Silver
];
function getColorForProgress(progress: number): string {
- if (progress >= 1) return SLIDER_COLORS[SLIDER_COLORS.length - 1];
-
- const regularColorsCount = SLIDER_COLORS.length - 1;
- const index = Math.floor(progress * regularColorsCount);
- return SLIDER_COLORS[Math.min(index, regularColorsCount - 1)];
+ const index = Math.floor(progress * SLIDER_COLORS.length);
+ return SLIDER_COLORS[Math.min(index, SLIDER_COLORS.length - 1)];
}
const StarSlider = ({
maxValue,
defaultValue,
- minValue,
+ minValue: minValueProp,
minAllowedValue,
+ minAllowedProgress,
className,
floatingBadgeDescription,
shouldUseDynamicColor,
shouldAllowCustomValue,
onChange,
onBadgeClick,
+ onCustomValueClick,
}: OwnProps) => {
const containerRef = useRef();
const floatingBadgeContentRef = useRef();
const lang = useLang();
- const min = minValue ?? 1;
+ const baseMinValue = minValueProp ?? 1;
+
+ // Uses binary search - O(log n)
+ const actualMinValue = useMemo(() => {
+ if (!minAllowedProgress || !minAllowedValue || minAllowedValue <= baseMinValue) {
+ return baseMinValue;
+ }
+
+ let low = baseMinValue;
+ let high = minAllowedValue;
+
+ while (low < high) {
+ const mid = Math.floor((low + high) / 2);
+ const testPoints = buildPoints(mid, maxValue);
+ const testProgress = getProgress(testPoints, minAllowedValue, mid);
+ const normalizedProgress = testProgress / testPoints.length;
+
+ if (normalizedProgress < minAllowedProgress) {
+ high = mid;
+ } else {
+ low = mid + 1;
+ }
+ }
+
+ return Math.max(baseMinValue, low);
+ }, [baseMinValue, minAllowedValue, minAllowedProgress, maxValue]);
const points = useMemo(() => {
- const result = [];
-
- for (let i = 0; i < DEFAULT_POINTS.length; i++) {
- if (DEFAULT_POINTS[i] <= min) continue;
-
- if (DEFAULT_POINTS[i] < maxValue) {
- result.push(DEFAULT_POINTS[i]);
- }
-
- if (DEFAULT_POINTS[i] >= maxValue) {
- result.push(maxValue);
- return result;
- }
- }
-
- const lastPoint = DEFAULT_POINTS[DEFAULT_POINTS.length - 1];
- let nextPoint = lastPoint + LARGE_STEP;
- while (nextPoint < maxValue) {
- result.push(nextPoint);
- nextPoint += LARGE_STEP;
- }
- result.push(maxValue);
-
- return result;
- }, [maxValue, min]);
+ return buildPoints(actualMinValue, maxValue);
+ }, [maxValue, actualMinValue]);
const [value, setValue] = useState(0);
const [containerWidth, setContainerWidth] = useState(0);
@@ -129,14 +132,14 @@ const StarSlider = ({
const startXRef = useRef();
const prevBadgeWidth = usePrevious(badgeWidth);
- const badgeText = lang.number(getValue(points, value, min));
+ const badgeText = lang.number(getValue(points, value, actualMinValue));
- const minAllowedProgress = minAllowedValue !== undefined
- ? getProgress(points, minAllowedValue, min) : 0;
+ const minSliderProgress = minAllowedValue !== undefined
+ ? getProgress(points, minAllowedValue, actualMinValue) : 0;
useEffect(() => {
- setValue(getProgress(points, defaultValue, min));
- }, [defaultValue, points, min]);
+ setValue(getProgress(points, defaultValue, actualMinValue));
+ }, [defaultValue, points, actualMinValue]);
useEffect(() => {
if (!floatingBadgeContentRef.current) return;
@@ -183,10 +186,10 @@ const StarSlider = ({
const handleChange = useLastCallback((event: React.ChangeEvent) => {
const rawValue = Number(event.currentTarget.value);
- const clampedValue = Math.max(rawValue, minAllowedProgress);
+ const clampedValue = Math.max(rawValue, minSliderProgress);
setValue(clampedValue);
- const resultValue = getValue(points, clampedValue, min);
+ const resultValue = getValue(points, clampedValue, actualMinValue);
onChange(resultValue);
});
@@ -203,8 +206,20 @@ const StarSlider = ({
}
});
- const handlePointerUp = useLastCallback(() => {
+ const handlePointerUp = useLastCallback((e: React.PointerEvent) => {
startXRef.current = undefined;
+
+ if (!isDragging && onCustomValueClick && containerRef.current) {
+ const rect = containerRef.current.getBoundingClientRect();
+ const clickZoneWidth = 1.875 * REM;
+
+ const isInIconArea = e.clientX >= rect.right - clickZoneWidth && e.clientX <= rect.right;
+
+ if (isInIconArea) {
+ onCustomValueClick();
+ }
+ }
+
setIsDragging(false);
});
@@ -291,7 +306,7 @@ const StarSlider = ({
type="range"
min={0}
max={points.length}
- defaultValue={getProgress(points, defaultValue, min)}
+ defaultValue={getProgress(points, defaultValue, actualMinValue)}
step="any"
onChange={handleChange}
onPointerDown={handlePointerDown}
@@ -305,6 +320,35 @@ const StarSlider = ({
);
};
+function buildPoints(minValue: number, maxValue: number): number[] {
+ const result = [];
+
+ for (let i = 0; i < DEFAULT_POINTS.length; i++) {
+ if (DEFAULT_POINTS[i] <= minValue) continue;
+
+ if (DEFAULT_POINTS[i] < maxValue) {
+ result.push(DEFAULT_POINTS[i]);
+ }
+
+ if (DEFAULT_POINTS[i] >= maxValue) {
+ result.push(maxValue);
+ return result;
+ }
+ }
+
+ const lastPoint = DEFAULT_POINTS[DEFAULT_POINTS.length - 1];
+ const stepsNeeded = Math.ceil((minValue - lastPoint) / LARGE_STEP);
+ let nextPoint = lastPoint + Math.max(1, stepsNeeded) * LARGE_STEP;
+
+ while (nextPoint < maxValue) {
+ result.push(nextPoint);
+ nextPoint += LARGE_STEP;
+ }
+ result.push(maxValue);
+
+ return result;
+}
+
function getProgress(points: number[], value: number, minValue: number) {
const pointIndex = points.findIndex((point) => value <= point);
const prevPoint = points[pointIndex - 1] || minValue;