VibeCoderzVibeCoderz
Telegram
All Prompts
ui component

Counter

React-компонент счетчика с плавными механическими переходами. Идеален для отображения данных с эффектом одометра. Настраиваемый дизайн.

by Zhou JasonLive Preview

Prompt

# Counter

You are given a task to integrate an existing React component in the codebase

~~~/README.md
# RollingCounter

A premium, high-performance rolling counter component with smooth mechanical-style transitions. Features automatic digit detection, customizable font sizes, and sleek gradient overlays for a professional 'odometer' effect.

## Dependencies
- `framer-motion`: `^11.0.0`
- `lucide-react`: `latest` (for demo icons)

## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `value` | `number` | - | The numeric value to display. |
| `fontSize` | `number` | `100` | Font size in pixels. |
| `padding` | `number` | `0` | Vertical padding inside each digit container. |
| `places` | `PlaceValue[]` | - | Custom array of place values (e.g., `[100, 10, 1, '.', 0.1]`). |
| `gap` | `number` | `8` | Gap between digits. |
| `borderRadius` | `number` | `4` | Border radius of the counter container. |
| `horizontalPadding` | `number` | `8` | Horizontal padding of the counter container. |
| `textColor` | `string` | `'inherit'` | Text color. |
| `fontWeight` | `CSSProperties['fontWeight']` | `'inherit'` | Font weight. |
| `gradientHeight` | `number` | `16` | Height of the gradient overlay. |
| `gradientFrom` | `string` | `'hsl(var(--background))'` | Start color of the gradient. |

## Usage
```tsx
import { RollingCounter } from '@/sd-components/7d2b2a3f-6411-4777-94ce-a18ef1ceca98';

export default function MyComponent() {
  return (
    <RollingCounter 
      value={5432.1} 
      fontSize={64}
      textColor="#1A1A1B"
      gradientFrom="#F9F9F9"
    />
  );
}
```
~~~

~~~/src/App.tsx
/**
 * Demo for RollingCounter Component
 * 
 * Shows a minimalist showcase of the rolling counter effect.
 * The counter value increments automatically to demonstrate the motion.
 */

import React, { useState, useEffect } from 'react';
import { RollingCounter } from './Component';
import { RotateCcw } from 'lucide-react';

export default function App() {
  const [value, setValue] = useState(1234.5);

  useEffect(() => {
    const timer = setInterval(() => {
      setValue(v => parseFloat((v + 1.1).toFixed(1)));
    }, 2000);
    return () => clearInterval(timer);
  }, []);

  const resetValue = () => setValue(1234.5);

  return (
    <div className="min-h-screen bg-[#F9F9F9] flex flex-col items-center justify-center p-20">
      {/* Title as per design guidelines */}
      <h1 className="mb-12 text-sm uppercase tracking-widest text-muted-foreground font-medium">
        Rolling Counter
      </h1>

      {/* Main Showcase Container */}
      <div className="bg-white p-20 rounded-[40px] shadow-[0_40px_80px_rgba(0,0,0,0.05)] flex flex-col items-center gap-12">
        <RollingCounter 
          value={value} 
          fontSize={120}
          textColor="hsl(var(--foreground))"
          fontWeight={600}
          gradientFrom="white"
          gradientHeight={30}
          gap={4}
        />

        {/* Reply/Action Button as per guidelines */}
        <button 
          onClick={resetValue}
          className="flex items-center gap-2 px-6 py-3 bg-foreground text-background rounded-full hover:opacity-90 transition-opacity font-medium text-sm"
        >
          <RotateCcw className="w-4 h-4" />
          Reset Counter
        </button>
      </div>

      {/* Subtitle / Description */}
      <p className="mt-12 text-muted-foreground text-center max-w-md text-sm leading-relaxed">
        A precision mechanical-style animation using Framer Motion 
        with automatic place value detection and smooth transitions.
      </p>
    </div>
  );
}
~~~

~~~/package.json
{
  "name": "rolling-counter",
  "description": "A premium mechanical rolling counter component with smooth Framer Motion animations.",
  "dependencies": {
    "framer-motion": "^11.0.0",
    "lucide-react": "latest",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
~~~

~~~/src/Component.tsx
/**
 * RollingCounter Component
 * 
 * A high-performance, mechanical-style rolling counter using Framer Motion.
 * It features smooth digit transitions, automatic place value detection,
 * and customizable styling including gradient overlays for a polished look.
 */

import { MotionValue, motion, useSpring, useTransform } from 'framer-motion';
import React, { useEffect } from 'react';

type PlaceValue = number | '.';

interface NumberProps {
  mv: MotionValue<number>;
  number: number;
  height: number;
}

function Number({ mv, number, height }: NumberProps) {
  const y = useTransform(mv, latest => {
    const placeValue = latest % 10;
    const offset = (10 + number - placeValue) % 10;
    let memo = offset * height;
    if (offset > 5) {
      memo -= 10 * height;
    }
    return memo;
  });

  const baseStyle: React.CSSProperties = {
    position: 'absolute',
    inset: 0,
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center'
  };

  return <motion.span style={{ ...baseStyle, y }}>{number}</motion.span>;
}

interface DigitProps {
  place: PlaceValue;
  value: number;
  height: number;
  digitStyle?: React.CSSProperties;
}

function Digit({ place, value, height, digitStyle }: DigitProps) {
  // Decimal point digit
  if (place === '.') {
    return (
      <span
        className="relative inline-flex items-center justify-center"
        style={{ height, width: 'fit-content', ...digitStyle }}
      >
        .
      </span>
    );
  }

  // Numeric digit
  const valueRoundedToPlace = Math.floor(value / place);
  const animatedValue = useSpring(valueRoundedToPlace, {
    damping: 20,
    stiffness: 100,
    mass: 1
  });

  useEffect(() => {
    animatedValue.set(valueRoundedToPlace);
  }, [animatedValue, valueRoundedToPlace]);

  const defaultStyle: React.CSSProperties = {
    height,
    position: 'relative',
    width: '1ch',
    fontVariantNumeric: 'tabular-nums'
  };

  return (
    <span className="relative inline-flex overflow-hidden" style={{ ...defaultStyle, ...digitStyle }}>
      {Array.from({ length: 10 }, (_, i) => (
        <Number key={i} mv={animatedValue} number={i} height={height} />
      ))}
    </span>
  );
}

export interface RollingCounterProps {
  /** The numeric value to display */
  value: number;
  /** Font size in pixels (default: 100) */
  fontSize?: number;
  /** Vertical padding inside each digit container (default: 0) */
  padding?: number;
  /** 
   * Custom array of place values (e.g., [100, 10, 1, '.', 0.1]). 
   * If omitted, it's automatically detected from value.
   */
  places?: PlaceValue[];
  /** Gap between digits (default: 8) */
  gap?: number;
  /** Border radius of the counter container (default: 4) */
  borderRadius?: number;
  /** Horizontal padding of the counter container (default: 8) */
  horizontalPadding?: number;
  /** Text color (default: 'inherit') */
  textColor?: string;
  /** Font weight (default: 'inherit') */
  fontWeight?: React.CSSProperties['fontWeight'];
  /** Style object for the outer container */
  containerStyle?: React.CSSProperties;
  /** Style object for the inner counter wrapper */
  counterStyle?: React.CSSProperties;
  /** Style object for each individual digit span */
  digitStyle?: React.CSSProperties;
  /** Height of the gradient overlay (default: 16) */
  gradientHeight?: number;
  /** Start color of the gradient (default: 'black') */
  gradientFrom?: string;
  /** End color of the gradient (default: 'transparent') */
  gradientTo?: string;
  /** Custom style for the top gradient */
  topGradientStyle?: React.CSSProperties;
  /** Custom style for the bottom gradient */
  bottomGradientStyle?: React.CSSProperties;
}

/**
 * A reusable rolling counter component with smooth mechanical motion.
 */
export function RollingCounter({
  value,
  fontSize = 100,
  padding = 0,
  places,
  gap = 8,
  borderRadius = 4,
  horizontalPadding = 8,
  textColor = 'inherit',
  fontWeight = 'inherit',
  containerStyle,
  counterStyle,
  digitStyle,
  gradientHeight = 16,
  gradientFrom = 'hsl(var(--background))',
  gradientTo = 'transparent',
  topGradientStyle,
  bottomGradientStyle
}: RollingCounterProps) {
  const height = fontSize + padding;

  // Automatic place detection if none provided
  const derivedPlaces = places || [...value.toString()].map((ch, i, a) => {
    if (ch === '.') return '.';
    const dotIndex = a.indexOf('.');
    const isInteger = dotIndex === -1;
    const exponent = isInteger ? a.length - i - 1 : i < dotIndex ? dotIndex - i - 1 : -(i - dotIndex);
    return 10 ** exponent;
  });

  const defaultContainerStyle: React.CSSProperties = {
    position: 'relative',
    display: 'inline-block'
  };

  const defaultCounterStyle: React.CSSProperties = {
    fontSize,
    display: 'flex',
    gap,
    overflow: 'hidden',
    borderRadius,
    paddingLeft: horizontalPadding,
    paddingRight: horizontalPadding,
    lineHeight: 1,
    color: textColor,
    fontWeight
  };

  const gradientContainerStyle: React.CSSProperties = {
    pointerEvents: 'none',
    position: 'absolute',
    inset: 0,
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'space-between'
  };

  const defaultTopGradientStyle: React.CSSProperties = {
    height: gradientHeight,
    background: `linear-gradient(to bottom, ${gradientFrom}, ${gradientTo})`,
    zIndex: 10
  };

  const defaultBottomGradientStyle: React.CSSProperties = {
    height: gradientHeight,
    background: `linear-gradient(to top, ${gradientFrom}, ${gradientTo})`,
    zIndex: 10
  };

  return (
    <span style={{ ...defaultContainerStyle, ...containerStyle }}>
      <span style={{ ...defaultCounterStyle, ...counterStyle }}>
        {derivedPlaces.map((place, idx) => (
          <Digit 
            key={`${place}-${idx}`} 
            place={place} 
            value={value} 
            height={height} 
            digitStyle={digitStyle} 
          />
        ))}
      </span>
      <span style={gradientContainerStyle}>
        <span style={topGradientStyle ?? defaultTopGradientStyle} />
        <span style={bottomGradientStyle ?? defaultBottomGradientStyle} />
      </span>
    </span>
  );
}

export default RollingCounter;
~~~

Implementation Guidelines

1. Analyze the component structure, styling, animation implementations
2. Review the component's arguments and state
3. Think through what is the best place to adopt this component/style into the design we are doing
4. Then adopt the component/design to our current system

Help me integrate this into my design
All Prompts