pqina / flip

⏳ The online version of the classic flip clock
https://pqina.nl/flip/
MIT License
892 stars 87 forks source link

Adding Flip Spans in the runtime #71

Closed Dav1d-Fn closed 5 months ago

Dav1d-Fn commented 5 months ago

Hello there,

I'm working on a React project and have a problem with creating new Flip Elements in the runtime. In my code I want to have a dateTimeString for custom DateTime formats. My code is generating the elements properly but new spans are not getting updated. I'm not sure if I'm using the library correct. Is this a common way how to use it?:

import Tick from "@pqina/flip";
import "@pqina/flip/dist/flip.min.css";
import "./flipclockstyles.css";

import React, { useEffect, useRef, useCallback, useMemo } from 'react';
import { atom, useAtom } from "jotai";
import { atomWithStorage } from "jotai/utils";
import dayjs from 'dayjs';

const boxBackgroundColorAtom = atomWithStorage('boxBackgroundColor', '#1a1a1a00')
const boxRoundedAtom = atomWithStorage('boxRounded', 20)
const textColorAtom = atomWithStorage('textColor', '#ffffffff')
const seperationColorAtom = atomWithStorage('seperationColor', '#000000ff')
const flipCardBackgroundAtom = atomWithStorage('flipCardBackground', '#000000ff')
const flipcardRoundedAtom = atomWithStorage('flipcardRounded', 20)
//clockWidth is an integer with represents the width of the window
const clockWidthAtom = atomWithStorage('clockWidth', 100)
const clockPaddingAtom = atomWithStorage('clockPadding', 0)

const dateTimeStringAtom = atomWithStorage("dateTimeString","hhss")

export default function FlipClock() {   

  const [boxBackgroundColor, ] = useAtom(boxBackgroundColorAtom);
  const [boxRounded, setBoxRounded] = useAtom(boxRoundedAtom);
  const [textColor, setTextColor] = useAtom(textColorAtom);
  const [seperationColor, setSeperationColor] = useAtom(seperationColorAtom);
  const [flipCardBackground, setFlipCardBackground] = useAtom(flipCardBackgroundAtom);
  const [flipcardRounded, setFlipcardRounded] = useAtom(flipcardRoundedAtom);
  const [clockWidth, setClockWidth] = useAtom(clockWidthAtom);
  const [clockPadding, setClockPadding] = useAtom(clockPaddingAtom);
  const [dateTimeString, setDateTimeString] = useAtom(dateTimeStringAtom);

  const tickRef = useRef();
  const flipClockDifRef = useRef(undefined)

  function percentage_to_em_string(percentage) {
    return (percentage / 100).toString() + "em";
  }

  function percentage_padding_to_vh_string (percentage) {
    return (percentage * 0.3 + 5).toString() + "vh";
  }

  function getWindowHeight() {
    var elem = document.getElementById('flipclockdiv')
    if(elem) {
      return elem.clientHeight+5;
    } else {
      return 100;
    }
  } 

  function percentage_box_rounded_to_px_string(percentage) {
    return ((percentage / 100) * getWindowHeight()).toString() + "px";
  }

  function getClockWidthString() { 
    const calculatedWidth = clockWidth - (2*clockMargin);
    return calculatedWidth.toString() + "px"; 
  }

  useEffect(() => {

    const tick = Tick.DOM.create(tickRef.current);
    Tick.DOM.parse(document.body);
    // Start interval (default is 1 second) and update clock with current time
    const intervalId = Tick.helper.interval(() => {

      const currentDateTimeString = dayjs().format('MMssHH');
      // Function to generate keys for each character
      const values = {};
      for (let i = 0; i < currentDateTimeString.length; i++) {
         values["c"+i] = currentDateTimeString.charAt(i);
      }

      tick.value = values;

    });

    // Cleanup function
    return () => {
      Tick.DOM.destroy(tickRef.current);
      clearInterval(intervalId);
    };
  }, []);

  const arrayDataItems = useMemo(() => {
  return dateTimeString.split("").map((_, index) => {
    return React.createElement(
      'span',
      {
        key: `span${index}`,
        'data-key': `c${index}`,
        'data-transform': "pad(0)",
        'data-view': "flip",
        className: "tick-flip"
      }
    );
  });
}, [dateTimeString]);

  return (
    <div style={{padding: percentage_padding_to_vh_string(clockPadding), backgroundColor: boxBackgroundColor, borderRadius: percentage_box_rounded_to_px_string(boxRounded)}}>
      <div style={{"--flipcard-bg-color": flipCardBackground, 
                    "--text-color": textColor, 
                    "--seperator-color": seperationColor,
                    "--flipcard-rounded": percentage_to_em_string(flipcardRounded)}} 
            ref={tickRef} data-did-init="handleTickInit" 
            data-credits="false"
            className="tick">
            <div id="flipclockdiv" data-layout="horizontal fit">
              {arrayDataItems}
            </div>
      </div>
    </div>
  );
}
rikschennink commented 5 months ago

If you adjust the DOM you have to re-initialise Flip.

Alternatively you can use the JS api to generate the DOM dynamically which might be a better option in this case. https://pqina.nl/tick/#api-javascript

Dav1d-Fn commented 5 months ago

Thank you for your answer. I tried a few more things, but none of them worked as well as I would have liked. Perhaps I haven't yet understood exactly how it works.

I have now found a workaround that is sufficient for my use case. I haven't worked with React that often, but now I've learned that if you change the key of the component, React forces a rerender and the JS library is reinitialized. This means I can now adjust the format and the clock is updated.

Dav1d-Fn commented 5 months ago

If someone is interested in the full code, it is published in the project at https://github.com/Dav1d-Fn/Fliq-Desktop-Flipclock