garronej / tss-react

✨ Dynamic CSS-in-TS solution, based on Emotion
https://tss-react.dev
MIT License
658 stars 37 forks source link

Ability to override generated classnames? #220

Closed jabakochashvilisunrun closed 2 days ago

jabakochashvilisunrun commented 2 days ago

Hello,

I’m curious if there’s a way to change the naming of the class names that are automatically generated by TSS. Before we upgraded MUI and switched to TSS, we used the createGenerateClassname (https://mui.com/system/styles/api/#creategenerateclassname-options-class-name-generator) function to override class names for our specific project needs. I couldn’t find anything similar for classes generated by TSS.

For example, this class name was generated by TSS: "css-1fsy1xq-FeatureItems-features". I'd like to be able to change the algorithm that's used to generate it if that's possible.

Thanks!

garronej commented 2 days ago

Hello @jabakochashvilisunrun,

While TSS doesn't currently allow a complete override of the algorithm that generates class names, it still offers a high degree of customization.

Let’s break down the structure of the generated class names:

css-1fsy1xq-FeatureItems-features
const useStyles = tss.create({ features: { /* your styles */ } });

// Then

const { classes } = useStyles();

<div className={classes.features} />

Let me know if this customization level meets your needs or if you’d like more granular control.

jabakochashvilisunrun commented 2 days ago

Thanks, ideally we'd like to have control over the automatically generated hash, but I understand that it cannot be controlled directly. that's what I wanted to check with you.

Thanks for the quick reply! closing this issue now

garronej commented 2 days ago

Well TSS has a plugin system that you could levrage. You could overwrite the classes object so that it returns your extact desired class on top of the class required.

You could have:

const useStyles = tss.withName("FeatureItems")create({ features: { /* your styles */ } });

// Then

const { classes } = useStyles();

<div className={classes.features} />

Be rendered as:

<div class="css-1fsy1xq-FeatureItems-features featureItems-features" />

For example.

Let me know if you're intrested, I can provide more details.

jabakochashvilisunrun commented 2 days ago

@garronej thanks for the example. Unfortunately that's not something we're looking for.

we'd like to change the algorithm that's used to generate automatically generated hash (1fsy1xq)

garronej commented 2 days ago

I think you can acheive this with a stylisPlugins plugin:

import createCache from '@emotion/cache';
import { CacheProvider } from '@emotion/react';
import stylis from 'stylis';

// Example of a custom hash plugin
const customHashPlugin = (element) => {
  if (element.type === 'rule') {
    element.props = element.props.map((selector) => {
      // Replace the hash generation with your custom logic
      // For example, prepend a custom prefix or apply a different hashing function
      return selector.replace(/css-[a-z0-9]+/, 'custom-prefix-' + generateCustomHash(selector));
    });
  }
};

// Your custom hashing function
function generateCustomHash(input) {
  // Implement your custom hash function here
  return input.length.toString(16); // This is a simple example; replace with a real hash
}

// Set up Emotion cache with the custom stylis plugin
const cache = createCache({
  key: 'custom',
  stylisPlugins: [customHashPlugin],
});

// Then wrap your app with CacheProvider and the custom cache
function App() {
  return (
    <CacheProvider value={cache}>
      {/* Your application */}
    </CacheProvider>
  );
}

(It's chatGPT so it probably need to be ajusted)

jabakochashvilisunrun commented 1 day ago

Thank you so much! I'll play with this code. I really appreciate this!

jabakochashvilisunrun commented 1 hour ago

@garronej I've played with this code, and I'm able to modify the hash value:

import createCache, { StylisElement } from '@emotion/cache';
import { CacheProvider } from '@emotion/react';

const hashOverridePlugin = (element: StylisElement) => {
  const props = element.props;
  const propsArr = typeof props === 'string' ? [props] : props;

  if (element.type === 'rule') {
    element.value = element.value.replace(/jest-[a-z0-9]+/, 'jest-uuid');
    element.props = propsArr.map((selector: string) => {
      return selector.replace(/jest-[a-z0-9]+/, 'jest-uuid');
    });
  }
};

const cache = createCache({
  key: 'jest',
  stylisPlugins: [hashOverridePlugin, console.log],
});

const customJestRenderer = (ui: ReactNode) => {
  function Wrapper({ children }: { children: ReactNode }) {
    return (
      <CacheProvider value={cache}>
        <ThemeProvider theme={customTheme}>{children}</ThemeProvider>
      </CacheProvider>
    );
  }

  return rtlRender(ui, { wrapper: Wrapper });
};

I'm able to verify with console.log plugin that the classnames are updating. Issue is that looks like this only updates the class names in generated stylesheet. DOM nodes still have the old classnames with hashes in it.

Do you have any suggestions on how to override that easily? To add more context, we're trying to remove hashes from the jest snapshots.

Thank you