DEV Community

Cover image for Virtual PCF Controls are GA... but theming is not!
Riccardo Gregori
Riccardo Gregori

Posted on

Virtual PCF Controls are GA... but theming is not!

The Virtual PCF Controls, now generally available (GA), are among the most eagerly awaited features for Power Platform developers, and for good reason.

As someone who values consistent user interfaces, one of the primary challenges I often encounter when implementing Web Resources and/or PCF controls is related to styling. It is no secret that the theme used by Model-Driven Apps differs from the one employed in Canvas Apps, and both, in turn, differ from the default Fluent UI theme.

Differences between standard theme and Canvas/Model Driven One

In professional Power Apps development, this "inconsistency" often leaves clients with a subjective impression of poor quality. To mitigate this perception, developers frequently resort to manually adjusting CSS to create as much uniformity as possible across controls. However, this manual tweaking can be both time-consuming for developers who lack extensive frontend expertise and prone to requiring rework whenever Microsoft updates the design language of its applications.

One of the most exciting promises of Virtual PCFs directly addresses this pain point:

Wrapping Fluent UI v9 controls as a component is the easiest way to utilize modern theming because the modern theme is automatically applied to these controls.

Even if the Virtual PCFs are now GA, the page containing that statement has still the (preview) tag in it... πŸ€”

Does it truly deliver on this promise? Unfortunately, ❌ the answer is no ❌.

Testing this is straightforward. Simply create a Virtual PCF after ensuring you have the latest version of the PAC CLI installed. In my case, I attempted to create a custom DateTime Picker that allows specifying seconds. It is a very simple control:

/* eslint-disable @typescript-eslint/no-explicit-any */
import * as React from 'react';
import { Input, makeStyles, tokens } from '@fluentui/react-components';

export interface IDateTimePickerControlProps {
  theme?: any;
  value?: Date;
  isEnabled?: boolean;
  onChange?: (value?: Date) => void;
}

const useStyles = makeStyles({
  container: {
    display: 'grid',
    width: '100%',
    gridTemplateColumns: '1fr 1fr',
    '@media (max-width: 200px)': {
      gridTemplateColumns: '1fr',
    },
    overflow: 'none'
  }
});

const DateTimePickerControl: React.FC<IDateTimePickerControlProps> = ({ theme, value, isEnabled, onChange }) => {
  const [dateValue, setDateValue] = React.useState(value?.toISOString().slice(0, 10) || '');
  const [timeValue, setTimeValue] = React.useState(value?.toTimeString().slice(0, 8) || '');
  const styles = useStyles();

  const onDateChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
    const date = new Date(event.target.value);
    const time = value || new Date();
    date.setHours(time.getHours(), time.getMinutes(), time.getSeconds());
    onChange?.(date);
    setDateValue(date.toISOString().slice(0, 10));
  };

  const onTimeChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
    const date = value || new Date();
    const [hours, minutes, seconds] = event.target.value.split(':').map(Number);
    date.setHours(hours, minutes, seconds);
    onChange?.(date);
    setTimeValue(date.toTimeString().slice(0, 8));
  };

  return (
    <div className={styles.container} style={{ gap: tokens.spacingHorizontalS, rowGap: tokens.spacingVerticalS, columnGap: tokens.spacingHorizontalS }}>
      <Input
        type="date"
        value={dateValue}
        onChange={onDateChange}
        disabled={!isEnabled}
      />
      <Input
        type="time"
        value={timeValue}
        onChange={onTimeChange}
        disabled={!isEnabled}
        step={1}
      />
    </div>
  );
};

export default DateTimePickerControl;
Enter fullscreen mode Exit fullscreen mode

I've then put that control in a Form and...

Control placed in the form

The field Start date is the one that uses my own control, while End date uses the standard one. As you can see even if I've not put any custom style in my control, the default style does not match the one shipped in the form πŸ˜•.

😠 Conclusions

Let me say, I've always been quite disappointed by this lack of consistency, since the beginning. One would expect that a huge company would have started with a framework and stay stick to it... but no, unfortunately we're still swimming in a sea of stratified libraries and versions.

For how long should we continue tweaking manually the UI to make it look professional?

FAQ

Why haven't you used the standard DatePicker control shipped by Fluent UI 9?

Simply because it's not part of the standard set of controls, but it's shipped via "@fluentui/react-datepicker-compat", that is not part of the default Virtual PCF framework.

Have you tryed the approach suggested by Diana's post, by wrapping the control in a FluentProvider?

Even if in the official documentation is stated that wrapping is only required when you want to apply a theme that is different from the one of the app, I tried and... Yes, the resulting effect is exactly the same 😀.

Top comments (0)