Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to load different UI configurations based on the tenant? #378

Open
rpfelgueiras opened this issue Jan 3, 2024 · 5 comments
Open

How to load different UI configurations based on the tenant? #378

rpfelgueiras opened this issue Jan 3, 2024 · 5 comments

Comments

@rpfelgueiras
Copy link

I see that Hashnode is loading a different theme (or UI settings) based on the tenant.
How do I do the same?

@chrishoermann
Copy link

I currently have the same requirement and had no luck with radix-theme yet - see #379

@rpfelgueiras
Copy link
Author

@chrishoermann I was searching for a solution and I will probably go in this direction tailwindlabs/tailwindcss#11964

In build time generate the right theme configurations per tenant and place the output in the right server/folder.

@alexanderbnelson
Copy link

alexanderbnelson commented Jan 25, 2024

I have theming implemented in my app. It's a multi-faceted approach that involves a combination of dynamically importing css and objects with tailwind classes.

Essentially all you need to do is get your subdomain or domain from next/headers at the root layout. Then use that to dynamically load your required theming info.

export default async function RootLayout(props: LayoutProps) {
  
  const headersList = headers();
  const host = headersList.get('host');
  const site = await getSiteData(host);
  
  ...

In my case getSiteData() looks like this - I have my hostname parsing function here

// Extract subdomain helper
function extractSubdomain(domain: string) {

  if (domain.endsWith(`.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`)) {
    return domain.replace(`.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`, '');
  } else if (domain.endsWith('.localhost:3000')) {
    return domain.replace('.localhost:3000', '');
  }

  return null;
}

export async function getSiteData(domain: string | null) {
  if (!domain) {
    return console.log("No domain provided");;
  }

  const subdomain = extractSubdomain(domain);

  return await unstable_cache(
    async () => {
      return prisma.site.findUnique({
        where: subdomain ? { subdomain: subdomain } : { customDomain: domain },
        include: {
          user: true,
          activeTheme: true,
        },
      });
    },
    [`${domain}-metadata`],
    {
      revalidate: 900,
      tags: [`${domain}-metadata`],
    },
  )();
}

Colour and font tokens are set by CSS variables and called for at the root with a data attribute. Those CSS variables are also set into my tailwind.config, just like @rpfelgueiras has shown in the link he referenced.

Set your data attributes on your tag. This is where you want font definitions especially

<html
      { "data-color-theme": `theme-${themeName}-color` }
      { "data-font-theme": `theme-${themeName}-font` }
    >

Define CSS variables

@layer base {

  [data-color-theme='theme-themeName-color'] { 
    --background: 0 0% 99%;
    --foreground: 45 25% 5%;

    --primary: 45 25% 5%;
    --primary-foreground: 45 25% 98%;
    ...

Then, tailwind classes are put into a theme-{themeName}.tsx file and applied in the site template code after dynamically importing the tailwind classes

Theme file

const themeName: Theme = {
    name: "The Theme Name",
    global: {
      text: "text-primary font-light",
      buttonRadius: "rounded-full"
    },
    ...

Dynamic import. This depends on naming your files with the same values you're using in your database to identify a site's theme.

const themeData = await import(`@/styles/themes/theme-${themeName}/theme-${themeName}`);`

Applied in components

<div className={cn(theme.profile.container)}>
        <p className={cn(theme.profile.name)}>{data?.displayName}</p>
        <div className={cn(theme.profile.infoContainer)}>
          ...

In my case, I'm not swapping out large chunks of JSX (yet). I'm just changing style. But there's no stopping you from having entire components conditionally render this way. It's a bit tedious to set up initially. But once the architecture is done, creating new themes is a bit less work.

@tomgreeEn
Copy link

I have a ServerSideThemeWrapper wrapping everything in the [domain] route, which has access to the relevant Site, from which I pass the theme props.

Using shadcn/ui:


export function ServersideThemeWrapper({
  children,
  className,
  color,
  radius,
}: ServersideThemeWrapperProps) {
  return (
    <div
      className={cn(`theme-${color}`, "w-full", className)}
      style={
        {
          "--radius": `${radius ? radius : 0.5}rem`,
        } as React.CSSProperties
      }
    >
      {children}
    </div>
  );
}

@mworks-proj
Copy link

I have a ServerSideThemeWrapper wrapping everything in the [domain] route, which has access to the relevant Site, from which I pass the theme props.

Using shadcn/ui:




export function ServersideThemeWrapper({

  children,

  className,

  color,

  radius,

}: ServersideThemeWrapperProps) {

  return (

    <div

      className={cn(`theme-${color}`, "w-full", className)}

      style={

        {

          "--radius": `${radius ? radius : 0.5}rem`,

        } as React.CSSProperties

      }

    >

      {children}

    </div>

  );

}



Mind sharing repo projects so far?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants