pnp / sp-dev-fx-controls-react

Reusable React controls for SPFx solutions
https://pnp.github.io/sp-dev-fx-controls-react/
MIT License
383 stars 378 forks source link

TextField css: How can I get a text field to animate when another function updates it's value through state change? #451

Open mikezimm opened 4 years ago

mikezimm commented 4 years ago

Category

[ ] Enhancement

[ ] Bug

[ x ] Question

Version

"@pnp/spfx-controls-react": "1.16.0"

Expected / Desired Behavior / Question

My scenario: I have a list of projects on my web part (using ListView control). When I click on one of the list items, I want it to update some text fields (via changing the state). When it auto-populates the field, I want to highlight to the user what fields changed.

I found an example of how to do this through css here

My 2 questions are: 1) Currently, I can get it to "highlight/blink" on page load and then fades (desired effect) but I want to have it do that only when I update the text field through my callback function, not on page load.

2) With the current css class, it highlights the TextField label, not the value/box. I would like it to highlight the box. I assume this is just finding the css syntax to apply it to a sibling/child element somehow?

Observed Behavior

1) Styling flashes/blinks on page load and I only want it to change when I call my function to update the state of a field.

2) I want it to highlight the value box, not the label.

Steps to Reproduce

Code I have for building the TextField

NOTE that the callback function I am using to pre-populate the field is not the one I'm passing in here as onChanged={ updateField }.... The function that is setting the value of the text field is in my main react component class.

 export function createBasicTextField(field: IFieldDef, currentValue, updateField){
   // it is possible to have an option to hide labels in lue of placeholder text for more compressed look
   let placeHolder = 'Enter ' + field.title;
   placeHolder = '';
    let textField = 
    <TextField
      className={ [styles.textField,styles.highlightBlink].join(' ') }
      defaultValue={ currentValue ? currentValue : "" }
      label={field.title}
      disabled={field.disabled}
      placeholder={ placeHolder }
      autoComplete='off'
      onChanged={ updateField }
      required={field.required}
    />;

    return textField;
  }

CSS classNames I'm injecting into className of TextField


  .textField {
    display: table-cell;
    width: 100%;

    input {
      height: 32px;
      min-width: 80px;
    }
  }

  .highlightBlink {
    position: relative;
    animation-name: slide;
    animation-duration: 0.5s;
    animation-timing-function: ease-in;
  }

  @keyframes slide {
    0% {
      background-color: white;
    }
    50% {
      background-color: lightgrey;
    }
    100% {
      background-color: white;
    }
  }
ghost commented 4 years ago

Thank you for reporting this issue. We will be triaging your incoming issue as soon as possible.

mikezimm commented 4 years ago

I was able to get the Label to blink but it was painfully un-elegant coding but have not found the secret to applying a classname to the input box of the text field.

What I had to do so far was add a new state property called blinkOnProject as a number. During loading, it's set as value 0. Then each time I click on a project, my onChange function (updateField) includes the following to toggle between 1 and 2:

Callback function changes blinkOnProject.

    this.setState({ blinkOnProject: this.state.blinkOnProject === 1 ? 2 : 1 });  

Then my textField function adds one of 2 classes depending on what number blinkOnProject is currently set to.

The strange part is that to get it to flash more than once, I had to have 2 different classnames and also make sure that the css was actually different. If both toggle class styles flashed the same color of gray, it never blinked.

Updated textField builder function which updates the className based on current blinkOnProject value:


export function createBasicTextField(field: IFieldDef, currentValue, updateField, blinkOnProject){
   let placeHolder = 'Enter ' + field.title;
   let classes = [styles.textField];

   if (blinkOnProject === 1 && field.blinkOnProject === true ) {
    classes.push(styles.highlightBlink1)
   } else if (blinkOnProject === 2 && field.blinkOnProject === true ) {
    classes.push(styles.highlightBlink2);
   }

   let classNames = classes.join(' ');

   placeHolder = '';
    let textField = 
    <TextField
      //className={ [styles.textField, styles.highlightBlink].join(' ') }
      className={ classNames }
      defaultValue={ currentValue ? currentValue : "" }
      label={field.title}
      disabled={field.disabled}
      placeholder={ placeHolder }
      autoComplete='off'
      onChanged={ updateField }
      required={field.required}
    />;

    return textField;
  }

CSS classes


  .textField {
    display: table-cell;
    width: 100%;

    input {
      height: 32px;
      min-width: 80px;
    }
  }

  .highlightBlink1 {
    position: relative;
    animation-name: slide1;
    animation-duration: .5s;
    animation-timing-function: ease-in;
  }

  .highlightBlink2 {
    position: relative;
    animation-name: slide2;
    animation-duration: .5s;
    animation-timing-function: ease-in;
  }

  @keyframes slide1 {
    0% {      background-color: white;    }
    50% {      background-color: darkgrey;    }
    100% {      background-color: white;    }
  }

  @keyframes slide2 {
    0% {      background-color: white;    }
    50% {      background-color: rgb(153, 153, 153);    }
    100% {      background-color: white;    }
  }
mikezimm commented 4 years ago

Ok, just solved making the input field blink....

Is this the way to do it or is there a better way? Just seems like a lot of messy code even though it's functional.

Thanks for any input :)

 export function createBasicTextField(field: IFieldDef, currentValue, updateField, blinkOnProject){
   // it is possible to have an option to hide labels in lue of placeholder text for more compressed look
   let placeHolder = 'Enter ' + field.title;
   let classes = [styles.textField];

   if (blinkOnProject === 1 && field.blinkOnProject === true ) {
    classes = [styles.textField1];
   } else if (blinkOnProject === 2 && field.blinkOnProject === true ) {
    classes = [styles.textField2];
   }

   let classNames = classes.join(' ');

   placeHolder = '';
    let textField = 
    <TextField
      //className={ [styles.textField, styles.highlightBlink].join(' ') }
      className={ classNames }
      defaultValue={ currentValue ? currentValue : "" }
      label={field.title}
      disabled={field.disabled}
      placeholder={ placeHolder }
      autoComplete='off'
      onChanged={ updateField }
      required={field.required}
    />;

    return textField;
  }

Updated css - basically just create 3 different parent classes for the textField and added @extend to each of them bringing in the animation into the input class directly.

Hokey but works.


  .textField1 {
    display: table-cell;
    width: 100%;

    input {
      @extend .highlightBlink1;
      height: 32px;
      min-width: 80px;
    }
  }

  .textField2 {
    display: table-cell;
    width: 100%;

    input {
      @extend .highlightBlink2;
      height: 32px;
      min-width: 80px;
    }
  }

  .highlightBlink1 {
    position: relative;
    animation-name: slide1;
    animation-duration: .5s;
    animation-timing-function: ease-in;
  }

  .highlightBlink2 {
    position: relative;
    animation-name: slide2;
    animation-duration: .5s;
    animation-timing-function: ease-in;
  }

  @keyframes slide1 {
    0% {      background-color: white;    }
    50% {      background-color: darkgrey;    }
    100% {      background-color: white;    }
  }

  @keyframes slide2 {
    0% {      background-color: white;    }
    50% {      background-color: rgb(153, 153, 153);    }
    100% {      background-color: white;    }
  }