반짝임 동기화

airwalk·2024년 1월 9일
post-thumbnail

테두리가 반짝이는 카드가 있다.
상태가 바뀌면 테두리 색이 바뀐다.
변경된 카드와 나머지 카드의 반짝이는 타이밍이 맞지 않다.
이것을 keyframes가 아닌 useState로 반짝임을 관리하도록 수정했다.

문제 코드

  • App.jsx
import React, { useState } from "react";
import SensorCard from "./Sensor";

const data = new Array(10).fill(undefined).map((item, index) => {
  return {
    name: `sensor${index}`,
    serial: `serial${index}`,
    status: "normal",
    index,
  };
});

export default function App() {
  const [sensorList, setSensorList] = useState(data);

  return (
    <div className=' '>
      <div className='grid gap-7 md:gap-3  grid-cols-10 3xl:grid-cols-8 xl:grid-cols-9 md:grid-cols-8 sm:grid-cols-4'>
        {sensorList.map((item, i) => {
          return (
            <div
              key={i}
              className='mb-[25px] col-span-2 2xl:col-span-2 xl:col-span-3  md:col-span-4  '
            >
              <SensorCard
                data={item}
                contentFirst
                halfCircleIcon
                setSensorList={setSensorList}
              />
            </div>
          );
        })}
      </div>
    </div>
  );
}
  • Sensor.jsx
import styled, { css, keyframes } from "styled-components";

const color = {
  alert: "rgb(239 68 68)",
  warning: "rgb(249 115 22)",
  normal: "rgb(34 197 94)",
  inspection: "rgb(168 85 247)",
};

const blinkAnimation = (props) => keyframes`
  0%, 100% {
    box-shadow: 0 0 0 1px ${color[props.status]};
  }
  
  50% {
    box-shadow: none;
  }
  
`;

const BoxBlinkStyle = styled.div`
  ${(status) =>
    css`
      animation: ${blinkAnimation(status)} 1.2s infinite;
    `}
`;

export const borderColor = {
  total: "border-gray-500",
  normal: "border-green-500",
  alert: "border-red-500",
  warning: "border-orange-500",
  inspection: "border-purple-500",
};

export default function SensorCard({ data, className, setSensorList }) {
  const { name, serial, status, index } = data;

  return (
    <div className={className}>
      <BoxBlinkStyle
        status={status}
        className={`
          bg-white dark:bg-white10 py-[25px] px-[25px] pb-[12px] overflow-hidden rounded-10 relative text-[15px] text-theme-gray dak:text-white60 border `}
      >
        <div className='flex justify-between items-center text-md mb-3'>
          <span
            className={`text font-semibold text-body dark:text-white60 text-[15px]  `}
          >
            {name}
          </span>
          <span className='text-body dark:text-white60 cursor-pointer'></span>
        </div>
        <div className='flex justify-between mb-2'>
          <small className='text-gray-500 font-semibold'>{serial}</small>
          <div>
            <button
              type='button'
              className='text-xs font-semibold mb-0 cursor-pointer hover:text-red-300'
              onClick={() => {
                setSensorList((pre) =>
                  pre.map((item, idx) => {
                    if (idx === index) {
                      return {
                        ...item,
                        status: item.status === "normal" ? "alert" : "normal",
                      };
                    }
                    return item;
                  })
                );
              }}
            >
              변경
            </button>
          </div>
        </div>
      </BoxBlinkStyle>
    </div>
  );
}

수정된 코드

  • App.jsx
import React, { useState, useEffect } from "react";
import styled from "styled-components";

const sensorsData = [
  { id: 1, name: "Sensor 1", status: "normal" },
  { id: 2, name: "Sensor 1", status: "normal" },
  { id: 3, name: "Sensor 1", status: "normal" },
  { id: 4, name: "Sensor 1", status: "normal" },
  { id: 5, name: "Sensor 1", status: "normal" },
  { id: 6, name: "Sensor 1", status: "normal" },
  { id: 7, name: "Sensor 1", status: "normal" },
  { id: 8, name: "Sensor 1", status: "normal" },
  { id: 9, name: "Sensor 1", status: "normal" },
  { id: 10, name: "Sensor 1", status: "normal" },
  { id: 11, name: "Sensor 1", status: "normal" },
  { id: 12, name: "Sensor 1", status: "normal" },
  { id: 13, name: "Sensor 1", status: "normal" },
  { id: 14, name: "Sensor 1", status: "normal" },
  { id: 15, name: "Sensor 1", status: "normal" },
  // ... (나머지 센서 데이터)
];

const SensorCard = styled.div`
  border: 2px solid
    ${(props) => {
      return props.isBlinking
        ? props.status === "danger"
          ? "red"
          : "green"
        : "transparent";
    }};
  border-radius: 8px;
  padding: 16px;
  margin: 10px;
  position: relative;
  transition: border 1.2s ease;
`;

const ChangeButton = styled.button`
  margin-top: 8px;
  cursor: pointer;
`;

const App = () => {
  const [sensorData, setSensorData] = useState(sensorsData);
  const [flashing, setFlashing] = useState(true);

  const [isBlinking, setIsBlinking] = useState(false);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setIsBlinking((prevIsBlinking) => !prevIsBlinking);
    }, 1200); // 1200ms 간격으로 토글

    return () => {
      clearInterval(intervalId);
    };
  }, []);

  const handleStatusChange = (id) => {
    setSensorData((prevData) =>
      prevData.map((sensor) =>
        sensor.id === id
          ? {
              ...sensor,
              status: sensor.status === "danger" ? "normal" : "danger",
            }
          : sensor
      )
    );
  };

  return (
    <div>
      {sensorData.map((sensor) => (
        <SensorCard
          key={sensor.id}
          status={sensor.status}
          isBlinking={isBlinking}
        >
          <h3>{sensor.name}</h3>
          <p>Status: {sensor.status}</p>
          <ChangeButton onClick={() => handleStatusChange(sensor.id)}>
            Change Status
          </ChangeButton>
        </SensorCard>
      ))}
    </div>
  );
};

export default App;

0개의 댓글