easyblocks icon indicating copy to clipboard operation
easyblocks copied to clipboard

Feature: Tailwilnd support

Open timoconnellaus opened this issue 9 months ago • 4 comments

I want to start a discussion on tailwind support for easyblocks. I've been working on a POC of this. The way it works is this:

Add a special prop in the styles function tw that returns classnames for no code components

const ItemWrappers = values.DummyComponentCollection.map((c: any) => {
  return `bg-[${values.backgroundColor}]`;
});

styled: {
  Root: {}, // we still include an empty styles object - but I think this should be removed
  ItemWrappers: ItemWrappers.map((i: any) => ({})),
},
props: {
  tw: {
    Root: `bg-[${values.backgroundColor}] pt-[${values.padding}]`,
    ItemWrappers, // there is support for arrays
  },
},

We still use the no code components as usual - but now they have a className property automatically applied. For example, if we defined the backgroundColor of Root to #ffffff then the component deefined like this

<Root.type {...Root.props />

would have the className

className="bg-[#ffffff]"

And if we defined it using responsive values for lg = #000000 and xl = #ffffff the className would be

className="bg-[#000000] xl:bg-[#ffffff]"

in compileComponent.tsx we decode the $res object created in the props.tw and add it to a __className attribute on the styled object. This logic is complex and handles doing this:

  • using the devices to append tailwind classes with sm, lg etc. e.g sm:pt-10. This includes adhering to the tailwind convention of small being the default and then larger sizes are exceptions
  • generating a single className string bsaed on the various classes defined
  • handling arrays of components and applying classNames to the array

In ComponentBuilder.tsx we move the __className to a prop on the react element

To handle actually rendering the tailwind css we leave that actually up to the person using the library. To make that easier, we add a subscribe callback to the editorWindowAPI that allows for receiving events that happen e.g. renderableContent

export type EditorWindowAPI = {
  editorContext: EditorContextType;
  onUpdate?: () => void; // this function will be called by parent window when data is changed, child should "subscribe" to this function

  // these callbacks are used by the useEasyblocksEditor hook
  onUpdateCallbacks?: Array<
    (eventType: EditorWindowAPICallbackEventType) => void
  >;
  subscribe: (
    callback: (eventType: EditorWindowAPICallbackEventType) => void
  ) => void;
  unsubscribe: (
    callback: (eventType: EditorWindowAPICallbackEventType) => void
  ) => void;

  meta: CompilationMetadata;
  compiled: CompiledShopstoryComponentConfig;
  externalData: ExternalData;
};

This can be used in the the project calling the EasyblocksEditor to scan editorWindowAPI.compiled to look for tailwind classes and update the CSS

In our case we have this library to generate CSS (https://github.com/mhsdesign/jit-browser-tailwindcss) and then we update the CSS in nextjs like this

<style jsx global>
  {`
    ${css}
  `}
</style>

I will submit a PR for this after we have some done more testing to make sure it is all working

We're currently converting the example components to use tailwind

The rationale of putting the logic in the styles function is that this makes moving over existing components to use tailwind. It also makes converting the logic more straightforward. We had tried using a separate tailwind function but the extensive amount of logic to get it working cause tons of issues and edge cases to deal with. Integrating into styles was more reliable by far

timoconnellaus avatar May 14 '24 22:05 timoconnellaus