Giter Club home page Giter Club logo

pigment-css's Introduction

Pigment CSS

Pigment CSS is a zero-runtime CSS-in-JS library that extracts the colocated styles to their own CSS files at build time.

Getting started

Pigment CSS supports Next.js and Vite with support for more bundlers in the future. You must install the corresponding plugin, as shown below.

Why choose Pigment CSS

Thanks to recent advancements in CSS (like CSS variables and color-mix()), "traditional" CSS-in-JS solutions that process styles at runtime are no longer required for unlocking features like color transformations and theme variables which are necessary for maintaining a sophisticated design system.

Pigment CSS addresses the needs of the modern React developer by providing a zero-runtime CSS-in-JS styling solution as a successor to tools like Emotion and styled-components.

Compared to its predecessors, Pigment CSS offers improved DX and runtime performance (though at the cost of increased build time) while also being compatible with React Server Components. Pigment CSS is built on top of WyW-in-JS, enabling to provide the smoothest possible experience for Material UI users when migrating from Emotion in v5 to Pigment CSS in v6.

Start with Next.js

Quickstart

Use the following commands to quickly create a new Next.js project with Pigment CSS set up:

curl https://codeload.github.com/mui/pigment-css/tar.gz/master | tar -xz --strip=2  pigment-css/examples/pigment-css-nextjs-ts
cd pigment-css-nextjs-ts

Manual installation

npm install @pigment-css/react
npm install --save-dev @pigment-css/nextjs-plugin

Then, in your next.config.js file, import the plugin and wrap the exported config object:

const { withPigment } = require('@pigment-css/nextjs-plugin');

module.exports = withPigment({
  // ... Your nextjs config.
});

Finally, import the stylesheet in the root layout.tsx file:

 import type { Metadata } from 'next';
+import '@pigment-css/react/styles.css';

 export const metadata: Metadata = {
   title: 'Create Next App',
   description: 'Generated by create next app',
 };

 export default function RootLayout(props: { children: React.ReactNode }) {
   return (
     <html lang="en">
       <body>{props.children}</body>
     </html>
   );
 }

Start with Vite

Quickstart

Use the following commands to quickly create a new Vite project with Pigment CSS set up:

curl https://codeload.github.com/mui/pigment-css/tar.gz/master | tar -xz --strip=2 pigment-css/examples/pigment-css-vite-ts
cd pigment-css-vite-ts

Manual installation

npm install @pigment-css/react@next
npm install --save-dev @pigment-css/vite-plugin@next

Then, in your Vite config file, import the plugin and pass it to the plugins array as shown:

import { pigment } from '@pigment-css/vite-plugin';

export default defineConfig({
  plugins: [
    pigment(),
    // ... Your other plugins.
  ],
});

Finally, import the stylesheet in the root main.tsx file:

 import * as React from 'react';
 import { createRoot } from 'react-dom/client';
+import '@pigment-css/react/styles.css';
 import App from './App';

 const rootElement = document.getElementById('root');
 const root = createRoot(rootElement);

 root.render(
   <React.StrictMode>
     <App />
   </React.StrictMode>,
 );

Basic usage

You must configure Pigment CSS with Next.js or Vite first.

Creating styles

Use the css API to create reusable styles:

import { css } from '@pigment-css/react';

const visuallyHidden = css({
  border: 0,
  clip: 'rect(0 0 0 0)',
  height: '1px',
  margin: -1,
  overflow: 'hidden',
  padding: 0,
  position: 'absolute',
  whiteSpace: 'nowrap',
  width: '1px',
});

function App() {
  return <div className={visuallyHidden}>I am invisible</div>;
}

The call to the css function is replaced with a unique string that represents the CSS class name for the generated styles.

Use a callback function to get access to the theme values:

const title = css(({ theme }) => ({
  color: theme.colors.primary,
  fontSize: theme.spacing.unit * 4,
  fontFamily: theme.typography.fontFamily,
}));

Creating components

Use the styled API to create a component by passing styles at the end. The usage should be familiar if you've worked with Emotion or styled-components:

import { styled } from '@pigment-css/react';

const Heading = styled('div')({
  fontSize: '4rem',
  fontWeight: 'bold',
  padding: '10px 0px',
});

function App() {
  return <Heading>Hello</Heading>;
}

Pigment CSS differs from "standard" runtime CSS-in-JS libraries in a few ways:

  1. You never get direct access to props in your styled declarations. This is because prop values are only available at runtime, but the CSS is extracted at build time. See Styling based on runtime values for a workaround.
  2. Your styles must be declarative and must account for all combinations of props that you want to style.
  3. The theme lets you declare CSS tokens that become part of the CSS bundle after the build. Any other values and methods that it might have are only available during build time—not at runtime. This leads to smaller bundle sizes.

Styling based on props

💡 This approach is recommended when the value of the prop is known at build time (finite values).

Use the variants key to define styles for a combination of the component's props.

Each variant is an object with props and style keys. The styles are applied when the component's props match the props object.

Example 1 — A button component with small and large sizes:

interface ButtonProps {
  size?: 'small' | 'large';
}

const Button = styled('button')<ButtonProps>({
  border: 'none',
  padding: '0.75rem',
  // ...other styles
  variants: [
    {
      props: { size: 'large' },
      style: { padding: '1rem' },
    },
    {
      props: { size: 'small' },
      style: { padding: '0.5rem' },
    },
  ],
});

<Button>Normal button</Button>; // padding: 0.75rem
<Button size="large">Large button</Button>; // padding: 1rem
<Button size="small">Small button</Button>; // padding: 0.5rem

Example 2 — A button component with variants and colors:

const Button = styled('button')({
  border: 'none',
  padding: '0.75rem',
  // ...other base styles
  variants: [
    {
      props: { variant: 'contained', color: 'primary' },
      style: { backgroundColor: 'tomato', color: 'white' },
    },
  ],
});

// `backgroundColor: 'tomato', color: 'white'`
<Button variant="contained" color="primary">
  Submit
</Button>;

Example 3 — Apply styles based on a condition:

The value of the props can be a function that returns a boolean. If the function returns true, the styles are applied.

const Button = styled('button')({
  border: 'none',
  padding: '0.75rem',
  // ...other base styles
  variants: [
    {
      props: (props) => props.variant !== 'contained',
      style: { backgroundColor: 'transparent' },
    },
  ],
});

Note that the props function doesn't work if it is inside another closure, for example, inside an array.map:

const Button = styled('button')({
  border: 'none',
  padding: '0.75rem',
  // ...other base styles
  variants: ['red', 'blue', 'green'].map((item) => ({
    props: (props) => {
      // ❌ Cannot access `item` in this closure
      return props.color === item && !props.disabled;
    },
    style: { backgroundColor: 'tomato' },
  })),
});

Instead, use plain objects to define the variants:

const Button = styled('button')({
  border: 'none',
  padding: '0.75rem',
  // ...other base styles
  variants: ['red', 'blue', 'green'].map((item) => ({
    props: { color: item, disabled: false },
    style: { backgroundColor: 'tomato' },
  })),
});

Styling based on runtime values

💡 This approach is recommended when the value of a prop is unknown ahead of time or possibly unlimited values, for example styling based on the user's input.

There are two ways to achieve this (both involve using a CSS variable):

  1. Declare a CSS variable directly in the styles and set its value using inline styles:
const Heading = styled('h1')({
  color: 'var(--color)',
});

function Heading() {
  const [color, setColor] = React.useState('red');

  return <Heading style={{ '--color': color }} />;
}
  1. Use a callback function as a value to create a dynamic style for the specific CSS property:
const Heading = styled('h1')({
  color: ({ isError }) => (isError ? 'red' : 'black'),
});

Pigment CSS replaces the callback with a CSS variable and injects the value through inline styles. This makes it possible to create a static CSS file while still allowing dynamic styles.

.Heading_class_akjsdfb {
  color: var(--Heading_class_akjsdfb-0);
}
<h1
  style={{
    '--Heading_class_akjsdfb-0': isError ? 'red' : 'black',
  }}
>
  Hello
</h1>

Styled component as a CSS selector

All of the components that you create are also available as CSS selectors. For example, for the Heading component described in the previous section, you can target that component inside another styled component like this:

const Wrapper = styled.div({
  [`& ${Heading}`]: {
    color: 'blue',
  },
});

This enables you to override the default color of the Heading component rendered inside the Wrapper:

<Wrapper>
  <Heading>Hello</Heading>
</Wrapper>

You can also export any styled component you create and use it as the base for additional components:

const ExtraHeading = styled(Heading)({
  // ... overridden styled
});

Media and Container queries

Pigment CSS APIs have built-in support for writing media queries and container queries. Use the @media and @container keys to define styles for different screen and container sizes.

import { css, styled } from '@pigment-css/react';

const styles = css({
  fontSize: '2rem',
  '@media (min-width: 768px)': {
    fontSize: '3rem',
  },
  '@container (max-width: 768px)': {
    fontSize: '1.5rem',
  },
});

const Heading = styled('h1')({
  fontSize: '2rem',
  '@media (min-width: 768px)': {
    fontSize: '3rem',
  },
  '@container (max-width: 768px)': {
    fontSize: '1.5rem',
  },
});

💡 Good to know:

Pigment CSS uses Emotion behind the scenes for turning tagged templates and objects into CSS strings.

Typing props

If you use TypeScript, add the props typing before the styles to get the type checking:

const Heading = styled('h1')<{ isError?: boolean }>({
  color: ({ isError }) => (isError ? 'red' : 'black'),
});

Creating animation keyframes

Use the keyframes API to create reusable animation keyframes:

import { keyframes } from '@pigment-css/react';

const fadeIn = keyframes`
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
`;

function Example1() {
  return <div style={{ animation: `${fadeIn} 0.5s` }}>I am invisible</div>;
}

The call to the keyframes function is replaced with a unique string that represents the CSS animation name. It can be used with css or styled too.

import { css, styled, keyframes } from '@pigment-css/react';

const fadeIn = keyframes(...);

const Example2 = styled('div')({
  animation: `${fadeIn} 0.5s`,
});

function App() {
  return (
    <>
      <Example1 />
      <div
        className={css`
          animation: ${fadeIn} 0.5s;
        `}
      />
    </>
  )
}

Theming

Theming is an optional feature that lets you reuse the same values, such as colors, spacing, and typography, across your application. It is a plain object of any structure that you can define in your config file.

💡 Good to know:

The theme object is used at build time and does not exist in the final JavaScript bundle. This means that components created using Pigment CSS's styled can be used with React Server Components by default while still getting the benefits of theming.

For example, in Next.js, you can define a theme in the next.config.js file like this:

const { withPigment } = require('@pigment-css/nextjs-plugin');

module.exports = withPigment(
  {
    // ...other nextConfig
  },
  {
    theme: {
      colors: {
        primary: 'tomato',
        secondary: 'cyan',
      },
      spacing: {
        unit: 8,
      },
      typography: {
        fontFamily: 'Inter, sans-serif',
      },
      // ...more keys and values, it's free style!
    },
  },
);

Accessing theme values

A callback can be used with styled() and css() APIs to access the theme values:

const Heading = styled('h1')(({ theme }) => ({
  color: theme.colors.primary,
  fontSize: theme.spacing.unit * 4,
  fontFamily: theme.typography.fontFamily,
}));

CSS variables support

Pigment CSS can generate CSS variables from the theme values when you wrap your theme with extendTheme utility. For example, in a next.config.js file:

const { withPigment, extendTheme } = require('@pigment-css/nextjs-plugin');

module.exports = withPigment(
  {
    // ...nextConfig
  },
  {
    theme: extendTheme({
      colors: {
        primary: 'tomato',
        secondary: 'cyan',
      },
      spacing: {
        unit: 8,
      },
      typography: {
        fontFamily: 'Inter, sans-serif',
      },
    }),
  },
);

The extendTheme utility goes through the theme and creates a vars object which represents the tokens that refer to CSS variables.

const theme = extendTheme({
  colors: {
    primary: 'tomato',
    secondary: 'cyan',
  },
});

console.log(theme.colors.primary); // 'tomato'
console.log(theme.vars.colors.primary); // 'var(--colors-primary)'

Adding a prefix to CSS variables

You can add a prefix to the generated CSS variables by providing a cssVarPrefix option to the extendTheme utility:

extendTheme({
  cssVarPrefix: 'pigment',
});

The generated CSS variables have the pigment prefix:

:root {
  --pigment-colors-background: #f9f9f9;
  --pigment-colors-foreground: #121212;
}

Color schemes

Some tokens, especially color-related tokens, can have different values for different scenarios. For example in a daylight condition, the background color might be white, but in a dark condition, it might be black.

The extendTheme utility lets you define a theme with a special colorSchemes key:

extendTheme({
  colorSchemes: {
    light: {
      colors: {
        background: '#f9f9f9',
        foreground: '#121212',
      },
    },
    dark: {
      colors: {
        background: '#212121',
        foreground: '#fff',
      },
    },
  },
});

In the above example, light (default) and dark color schemes are defined. The structure of each color scheme must be a plain object with keys and values.

Switching color schemes

By default, when colorSchemes is defined, Pigment CSS uses the prefers-color-scheme media query to switch between color schemes based on the user's system settings.

However, if you want to control the color scheme based on application logic, for example, using a button to switch between light and dark mode, you can customize the behavior by providing a getSelector function:

  extendTheme({
    colorSchemes: {
      light: { ... },
      dark: { ... },
    },
+   getSelector: (colorScheme) => colorScheme ? `.theme-${colorScheme}` : ':root',
  });

Note that you need to add the logic to a button by yourself. Here is an example of how to do it:

function App() {
  return (
    <button
      onClick={() => {
        document.documentElement.classList.toggle('theme-dark');
      }}
    >
      Toggle color scheme
    </button>
  );
}

Styling based on color scheme

The extendTheme utility attaches a function called applyStyles to the theme object. It receives a color scheme as the first argument followed by a style object. It returns a proper CSS selector based on the theme configuration.

const Heading = styled('h1')(({ theme }) => ({
  color: theme.colors.primary,
  fontSize: theme.spacing.unit * 4,
  fontFamily: theme.typography.fontFamily,
  ...theme.applyStyles('dark', {
    color: theme.colors.primaryLight,
  }),
}));

TypeScript

To get the type checking for the theme, you need to augment the theme type:

// any file that is included in your tsconfig.json
import type { ExtendTheme } from '@pigment-css/react/theme';

declare module '@pigment-css/react/theme' {
  interface ThemeTokens {
    // the structure of your theme
  }

  interface ThemeArgs {
    theme: ExtendTheme<{
      colorScheme: 'light' | 'dark';
      tokens: ThemeTokens;
    }>;
  }
}

Right-to-left support

To support right-to-left (RTL) languages, add the dir="rtl" attribute to your app's <html> element or any other equivalent top level container. Then, update your bundler config as follows to generate styles for both directions:

Next.js

const { withPigment } = require('@pigment-css/nextjs-plugin');

// ...
module.exports = withPigment(nextConfig, {
  theme: yourCustomTheme,
  // CSS output option
  css: {
    // Specify your default CSS authoring direction
    defaultDirection: 'ltr',
    // Generate CSS for the opposite of the `defaultDirection`
    // This is set to `false` by default
    generateForBothDir: true,
  },
});

Vite

import { pigment } from '@pigment-css/vite-plugin';

export default defineConfig({
  plugins: [
    pigment({
      theme: yourTheme,
      css: {
        // Specify your default CSS authoring direction
        defaultDirection: 'ltr',
        // Generate CSS for the opposite of the `defaultDirection`
        // This is set to `false` by default
        generateForBothDir: true,
      },
    }),
    // ... other plugins.
  ],
});

Generated CSS

For example, if you've specified defaultDirection: 'ltr' and dir="rtl", and your authored CSS looks like this:

import { css } from '@pigment-css/react';

const className = css`
  margin-left: 10px,
  margin-right: 20px,
  padding: '0 10px 20px 30px'
`;

Then the actual CSS output would be:

.cmip3v5 {
  margin-left: 10px;
  margin-right: 20px;
  padding: 0 10px 20px 30px;
}

[dir='rtl'] .cmip3v5 {
  margin-right: 10px;
  margin-left: 20px;
  padding: 0 30px 20px 10px;
}

Custom dir selector

The default selector in the output CSS is [dir=rtl] or [dir=ltr]. You can customize it by passing an optional getDirSelector method to the css property in your bundler config:

    css: {
      getDirSelector(dir: string) {
        // return a custom selector you'd like to use
        return `:dir(${dir})`;
      },
    },

How-to guides

Coming from Emotion or styled-components

Emotion and styled-components are runtime CSS-in-JS libraries. What you write in your styles is what you get in the final bundle, which means the styles can be as dynamic as you want with bundle size and performance overhead trade-offs.

On the other hand, Pigment CSS extracts CSS at build time and replaces the JavaScript code with hashed class names and some CSS variables. This means that it has to know all of the styles to be extracted ahead of time, so there are rules and limitations that you need to be aware of when using JavaScript callbacks or variables in Pigment CSS's APIs.

Here are some common patterns and how to achieve them with Pigment CSS:

  1. Fixed set of styles

In Emotion or styled-components, you can use props to create styles conditionally:

const Flex = styled('div')((props) => ({
  display: 'flex',
  ...(props.vertical // ❌ Pigment CSS will throw an error
    ? {
        flexDirection: 'column',
        paddingBlock: '1rem',
      }
    : {
        paddingInline: '1rem',
      }),
}));

But in Pigment CSS, you need to define all of the styles ahead of time using the variants key:

const Flex = styled('div')((props) => ({
  display: 'flex',
  variants: [
    {
      props: { vertical: true },
      style: {
        flexDirection: 'column',
        paddingBlock: '1rem',
      },
    },
    {
      props: { vertical: false },
      style: {
        paddingInline: '1rem',
      },
    },
  ],
}));

💡 Keep in mind that the variants key is for fixed values of props, for example, a component's colors, sizes, and states.

  1. Programatically generated styles

For Emotion and styled-components, the styles are different on each render and instance because the styles are generated at runtime:

function randomBetween(min: number, max: number) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}
function generateBubbleVars() {
  return `
    --x: ${randomBetween(10, 90)}%;
    --y: ${randomBetween(15, 85)}%;
  `;
}

function App() {
  return (
    <div>
      {[...Array(10)].map((_, index) => (
        <div key={index} className={css`${generateBubbleVars()}`} />
      ))}
    </div>
  )
}

However, in Pigment CSS with the same code as above, all instances have the same styles and won't change between renders because the styles are extracted at build time.

To achieve the same result, you need to move the dynamic logic to props and pass the value to CSS variables instead:

function randomBetween(min: number, max: number) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

const Bubble = styled('div')({
  '--x': props => props.x,
  '--y': props => props.y,
});

function App() {
  return (
    <div>
      {[...Array(10)].map((_, index) => (
        <Bubble key={index} x={`${randomBetween(10, 90)}%`} y={`${randomBetween(15, 85)}%`} />
      ))}
    </div>
  )
}

Building reusable components for UI libraries

The purpose of this guide is to demonstrate how to create reusable components for a UI library that can be shared across multiple projects and used to implement different design systems through custom theming. The approach outlined here is not necessary when constructing components to be consumed and themed in a single project. It's most relevant for developers who want to build a component library that could be published as a package to be consumed and themed by other developers.

The steps below will walk you through how to create a statistics component that could serve as part of a reusable UI library built with Pigment CSS. This process has three parts:

  1. Create component slots.
  2. Compose slots to create the component.
  3. Style slots based on props.

1. Create component slots

Slots let the consumers customize each individual element of the component by targeting its respective name in the theme's styleOverrides.

This statistics component is composed of three slots:

  • root: the container of the component
  • value: the number to be displayed
  • unit: the unit or description of the value

💡 Though you can give these slots any names you prefer, we recommend using root for the outermost container element for consistency with the rest of the library.

Use the styled API with name and slot parameters to create the slots, as shown below:

// /path/to/Stat.js
import * as React from 'react';
import { styled } from '@pigment-css/react';

const StatRoot = styled('div', {
  name: 'PigmentStat', // The component name
  slot: 'root', // The slot name
})({
  display: 'flex',
  flexDirection: 'column',
  gap: '1rem',
  padding: '0.75rem 1rem',
  backgroundColor: '#f9f9f9',
  borderRadius: '8px',
  boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
  letterSpacing: '-0.025em',
  fontWeight: 600,
});

const StatValue = styled('div', {
  name: 'PigmentStat',
  slot: 'value',
})({
  font: '1.2rem "Fira Sans", sans-serif',
});

const StatUnit = styled('div', {
  name: 'PigmentStat',
  slot: 'unit',
})({
  font: '0.875rem "Fira Sans", sans-serif',
  color: '#121212',
});

2. Create the component

Assemble the component using the slots created in the previous step:

// /path/to/Stat.js
import * as React from 'react';

// ...slot styled-components

const Stat = React.forwardRef(function Stat(props, ref) {
  const { value, unit, ...other } = props;

  return (
    <StatRoot ref={ref} {...other}>
      <StatValue>{value}</StatValue>
      <StatUnit>{unit}</StatUnit>
    </StatRoot>
  );
});

export default Stat;

3. Style slots based on props

In this example, a prop named variant is defined to let consumers change the appearance of the Stat component.

Pass down the variant prop to <StatRoot> to style the root slot, as shown below:

 const Stat = React.forwardRef(function Stat(props, ref) {
+  const { value, unit, variant, ...other } = props;

   return (
-     <StatRoot ref={ref} {...other}>
-       <StatValue>{value}</StatValue>
-       <StatUnit>{unit}</StatUnit>
-     </StatRoot>
+     <StatRoot ref={ref} variant={variant} {...other}>
+       <StatValue>{value}</StatValue>
+       <StatUnit>{unit}</StatUnit>
+     </StatRoot>
   );
 });

Then you can use Pigment CSS variants API to style it when variant prop has a value of outlined:

 const StatRoot = styled('div', {
   name: 'PigmentStat',
   slot: 'root',
 })({
   display: 'flex',
   flexDirection: 'column',
   gap: '1rem',
   padding: '0.75rem 1rem',
   backgroundColor: '#f9f9f9',
   borderRadius: '8px',
   boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
   letterSpacing: '-0.025em',
   fontWeight: 600,
+  variants: [
+    {
+      props: { variant: 'outlined' },
+      style: {
+        border: `2px solid #e9e9e9`,
+      },
+    },
+  ],
 });

This completes the reusable statistics component. If this were a real UI library, the component would be ready to upload to a package registry so others could use it.

Consumer usage

Developers using your component must first install your package as well as the Pigment CSS packages that correspond to the framework they're using.

npm install your-package-name @pigment-css/react
npm install -D @pigment-css/nextjs-plugin

Next, they must set up Pigment CSS in their project:

// framework config file, for example next.config.js
const { withPigment } = require('@pigment-css/nextjs-plugin');

module.exports = withPigment(
  {
    // ... Your nextjs config.
  },
  { transformLibraries: ['your-package-name'] },
);

Finally, they must import the stylesheet in the root layout file:

// index.tsx
import '@pigment-css/react/styles.css';

Then they can use your component in their project:

import Stat from 'your-package-name/Stat';

function App() {
  return <Stat value={42} unit="km/h" variant="outlined" />;
}

Consumer theming

Developers can customize the component's styles using the theme's styleOverrides key and the component name and slots you defined in step 2. For example, the custom theme below sets the background color of the statistics component's root slot to tomato:

module.exports = withPigment(
  { ...nextConfig },
  {
    theme: {
      styleOverrides: {
        PigmentStat: {
          root: {
            backgroundColor: 'tomato',
          },
          value: {
            color: 'white',
          },
          unit: {
            color: 'white',
          },
        },
      },
    },
  },
);

Developers can also access theme values and apply styles based on the component's props using the variants key:

module.exports = withPigment(
  { ...nextConfig },
  {
    theme: {
      // user defined colors
      colors: {
        primary: 'tomato',
        primaryLight: 'lightcoral',
      },
      styleOverrides: {
        PigmentStat: {
          root: ({ theme }) => ({
            backgroundColor: 'tomato',
            variants: [
              {
                props: { variant: 'outlined' },
                style: {
                  border: `2px solid ${theme.colors.primary}`,
                  backgroundColor: theme.colors.primaryLight,
                },
              },
            ],
          }),
          value: {
            color: 'white',
          },
          unit: {
            color: 'white',
          },
        },
      },
    },
  },
);

How Pigment CSS works

See How Pigment CSS works for details on the build process.

pigment-css's People

Contributors

renovate[bot] avatar brijeshb42 avatar mnajdova avatar siriwatknp avatar oliviertassinari avatar danilo-leal avatar mj12albert avatar aacevski avatar diegoandai avatar michaldudak avatar zeeshantamboli avatar dependabot[bot] avatar sai6855 avatar aarongarciah avatar janpot avatar zanivan avatar alexfauquette avatar jherr avatar lhilgert9 avatar mohammadshehadeh avatar

Stargazers

Mark Smith avatar Sunny Byte avatar Justin Abene avatar Sebastián García avatar HsunPei Wang avatar Malte Bastian avatar Jan R. Biasi avatar David Sundqvist avatar Jan Kott avatar Mohamed Abdelgwad avatar Guo avatar Sangbin Lee avatar Maxtune Lee avatar Gaetan SENELLE avatar Henry Yu avatar Yuki Tanaka avatar Makoto M avatar Sunny Singh avatar Timo Santi avatar  avatar Stanislav (Stanley) Modrak avatar Jon Insley avatar  avatar xyan avatar Ciaran Liedeman avatar Vincenzo Merolla avatar Ben Stickley avatar Joel Keyser avatar Karl Aleksandrov avatar Bernardo Andrade avatar Michiel Stragier avatar  avatar Alirexa avatar  avatar  avatar Evan Ye avatar Jimin (Aaron) Son avatar  avatar n0ir avatar José Adolfo Galdámez avatar Alain Kaiser avatar godsenal avatar Morrison Cole avatar zhupc avatar YohanS avatar patrick avatar Craig Patik avatar G4RDS avatar Phong Chu avatar  avatar Sam Walsh avatar Horatio Wang avatar Tao Jianhang avatar Flavien DELANGLE avatar Nguyễn Hoàng Trung avatar Aaron Heimlich avatar TRAN Duc Khanh avatar Dennis Morello avatar qingting avatar Iyasa avatar Olzhas Alexandrov avatar You Nguyen avatar Andrejs Agejevs avatar Akir Oussama avatar Yurii Rybak avatar Mehrdad Shokri avatar  avatar Guyang Li avatar  avatar Mahmood Bagheri avatar Mykhaylo Ryechkin avatar  avatar  avatar Luca Schneider avatar Rogin Farrer avatar Alex Johnson avatar mehdi avatar Douglas Zaltron avatar  avatar  avatar Colm avatar Ryota Murakami avatar  avatar Cicero Tiago avatar  avatar

Watchers

Matt avatar  avatar  avatar  avatar  avatar  avatar

pigment-css's Issues

[docs] Invalid CSS output w/ pseudo classes in theme.vars

Steps to reproduce

Steps:

  1. Add the following to theme.colorSchemes
theme.colorSchemes.dark.mixins.example = {
  "&:hover": {
    background: `rgba(0, 0, 0, 0.26)`,
  },
}
  1. Try to build; it will throw a warning [WARNING] Expected ":" [css-syntax-error] (it's actually an error; CSS is invalid & also won't be minified) during CSS minification, because the above ends up as --mixins-example-&:hover-background (notice the invalid & symbol in the variable name)

Your environment

npx @mui/envinfo

  System:
    OS: macOS 14.4
  Binaries:
    Node: 21.7.1 - /opt/homebrew/bin/node
    npm: 10.5.0 - /opt/homebrew/bin/npm
    pnpm: 8.15.5 - /opt/homebrew/bin/pnpm
  Browsers:
    Chrome: 123.0.6312.87
    Edge: 112.0.1722.39
    Safari: 17.4
  npmPackages:
    @pigment-css/react: 0.0.4
    @pigment-css/vite-plugin: 0.0.4
    @emotion/react:  11.11.4 
    @emotion/styled:  11.11.5 
    @mui/private-theming:  5.15.14 
    @mui/styled-engine:  6.0.0-alpha.0 
    @mui/system:  6.0.0-alpha.0 
    @mui/types:  7.2.14 
    @mui/utils:  5.15.14 
    @types/react: 18.2.73 => 18.2.73 
    react: ^18.2.0 => 18.2.0 
    react-dom: ^18.2.0 => 18.2.0 
    typescript: 5.4.3 => 5.4.3 

Search keywords: pigment theme vars

[core] Errors thrown in non-browser environment (unsafe when referenced)

Steps to reproduce

Link to live example

Steps:

  1. Clone repo
  2. Install deps npm install (from repo's root)
  3. Run npm run start (from pkg1)
  4. Observe error in your CLI
Error: @pigment-css/react: You were trying to call "css" function without configuring your bundler
  1. Comment out theme in vite.config.ts
  2. Restart npm run start (it will run without issues)

Current behavior

pigment-css-vite-plugin doesn't work when a theme is connected directly (imports a value) or imports a type from one of the files that is importing from a file with @pigment-css/react's css.

Expected behavior

Theme should be shareable and references shouldn't break the pigment-css-vite-plugin unexpectedly.

Context

Btw, the bug exists in both pigment's 0.0.4 and the latest state of mui's next branch (checked a couple of hours ago)

Your environment

npx @mui/envinfo

  System:
    OS: macOS 14.4
  Binaries:
    Node: 21.7.1 - /opt/homebrew/bin/node
    npm: 10.5.0 - /opt/homebrew/bin/npm
    pnpm: 8.15.5 - /opt/homebrew/bin/pnpm
  Browsers:
    Chrome: 123.0.6312.87
    Edge: 112.0.1722.39
    Safari: 17.4
  npmPackages:
    @pigment-css/react: 0.0.4
    @pigment-css/vite-plugin: 0.0.4
    @emotion/react:  11.11.4 
    @emotion/styled:  11.11.5 
    @mui/private-theming:  5.15.14 
    @mui/styled-engine:  6.0.0-alpha.0 
    @mui/system:  6.0.0-alpha.0 
    @mui/types:  7.2.14 
    @mui/utils:  5.15.14 
    @types/react: 18.2.73 => 18.2.73 
    react: ^18.2.0 => 18.2.0 
    react-dom: ^18.2.0 => 18.2.0 
    typescript: 5.4.3 => 5.4.3 

Search keywords: pigment vite monorepo

`css` function does not recognize `pointerEvents` (`pointer-events`) CSS property

Hello,

I was loonking to change the pointer-events property on a component with the following code:

const disableContextMenu = css({
  pointerEvents: "none",
});

But unfortunately, at build time, it throws the following error:

Type error: No overload matches this call.
  Overload 1 of 2, '(arg: TemplateStringsArray, ...templateArgs: (Primitve | CssFn)[]): string', gave the following error.
    Object literal may only specify known properties, and 'pointerEvents' does not exist in type 'TemplateStringsArray'.


const disableContextMenu = css({
>     pointerEvents: "none",
      ^
});

I'm a bit puzzled as pointer-events is a standard CSS property and using it in React's style prop works as expected.

I tried the following code as well, but it doesn't work either:

const disableContextMenu = css({
  "pointer-events": "none",
});
Aside

I'm not sure if it is relevant to this issue, but I think there should be a way to set non-standard CSS properties such as -webkit-touch-callout or experimental properties.

next info output
Operating System:
  Platform: linux
  Arch: x64
  Version: #28-Ubuntu SMP PREEMPT_DYNAMIC Thu Mar  7 18:21:00 UTC 2024
  Available memory (MB): 15685
  Available CPU cores: 8
Binaries:
  Node: 20.12.2
  npm: 10.5.0
  Yarn: N/A
  pnpm: 8.15.4
Relevant Packages:
  next: 14.2.0
  eslint-config-next: 14.2.0
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.4.3
  @pigment-css/react: 0.0.6
Next.js Config:
  output: N/A

[core] Cannot redefine `toString`

Steps to reproduce

Link to repo: https://github.com/siriwatknp/nextjs-mui-v6

Steps:

  1. pnpm install && pnpm run dev
  2. Go to localhost:3000, the page can be rendered
  3. Add this change, then save and you should see error in the terminal
diff --git a/src/app/checkout/page.tsx b/src/app/checkout/page.tsx
index 656df43..accc080 100644
--- a/src/app/checkout/page.tsx
+++ b/src/app/checkout/page.tsx
@@ -141,7 +141,7 @@ export default function Checkout() {
           <Box
             sx={{
               display: 'flex',
-              alignItems: 'end',
+              alignItems: 'flex-end',
               height: 150,
             }}
           >

Current behavior

image

Expected behavior

There should be no error.

Context

No response

Your environment

npx @mui/envinfo
  Don't forget to mention which browser you used.
  Output from `npx @mui/envinfo` goes here.

Search keywords: pigment-css redefine toString

When using use client with the @pigment-css/nextjs-plugin, an error occurs.

Steps to reproduce

  1. npx create-next-app@latest
  2. install @pigment-css/react @pigment-css/nextjs-plugin
  3. next.conifg.js
import { withPigment, extendTheme } from '@pigment-css/nextjs-plugin';

const nextConfig = {
    output: "export",
};

export default withPigment(nextConfig, {
  theme: extendTheme({
      colors: {
      primary: 'tomato',
      secondary: 'cyan',
      },
      spacing: {
      unit: 8,
      },
      typography: {
      fontFamily: 'Inter, sans-serif',
      },
  }),
});
  1. page.tsx
'use client'
import styles from "./page.module.css";
import { css } from "@pigment-css/react";

const description = css`
  display: inherit;
  justify-content: inherit;
  align-items: inherit;
  font-size: 0.85rem;
  max-width: var(--max-width);
  width: 100%;
  z-index: 1;
  font-family: var(--font-mono);
`;
......
export default function Home() {
 return (
    ......
      <div className={description}>Test</div>
    ......
 }
}

Current behavior

image

Expected behavior

No response

Context

No response

Your environment

System:
OS: macOS 14.3
Binaries:
Node: 20.12.2 - ~/.nvm/versions/node/v20.12.2/bin/node
npm: 10.5.0 - ~/.nvm/versions/node/v20.12.2/bin/npm
pnpm: 8.9.0 - ~/.nvm/versions/node/v20.12.2/bin/pnpm
Browsers:
Chrome: 124.0.6367.60
Edge: Not Found
Safari: 17.3
npmPackages:
@types/react: ^18 => 18.2.61
react: ^18 => 18.2.0
react-dom: ^18 => 18.2.0
typescript: ^5 => 5.3.3

Search keywords: @pigment-css/nextjs-plugin use client

[nextjs] Support Turbopack

Summary

When using next dev --turbo, I get the error: Error: @pigment-css/react: You were trying to call "css" function without configuring your bundler. because withPigment from @pigment-css/nextjs-plugin works by adding a webpack plugin which is not supported yet by Turbopack as stated in their docs: Will we be able to use webpack plugins?.

Examples

N/A

Motivation

I want to benefit from dev server performance improvements of Turbopack while using @pigment-css/react

Search keywords: pigment, turbopack

[bug] The `styled` function does not work with MUI system

Steps to reproduce

See https://github.com/mui/material-ui/actions/runs/8837080528/job/24265117421?pr=41663 in mui/material-ui#41663

Current behavior

ownerState is undefined in InputBase styles when FilledInput (Pigment) is wrapping Input (MUI System).

const FilledInputRoot = styled(InputBase)()

The root cause is that FilledInputRoot does not forward ownerState to InputBase which causes an error from the InputBase.

Expected behavior

They should work together, both MUI System wrappin Pigment or Pigment wrapping MUI System.

Context

Found this issue when trying to migrate only FilledInput to support Pigment CSS.

Your environment

npx @mui/envinfo
  Don't forget to mention which browser you used.
  Output from `npx @mui/envinfo` goes here.

Search keywords: styled

[sx] theme should not wrap in an object

Steps to reproduce

Render any component with sx usage like this will throw an Error Cannot read property 'applyStyles' of undefined:

<Box
  sx={theme => ({
    backgroundSize: 'cover',
    backgroundPosition: 'center',
    minHeight: 280,
    backgroundImage: 'var(--Image-light)',
    ...theme.applyStyles('dark', {
      backgroundImage: 'var(--Image-dark)',
    }),
  })}

Current behavior

The correct syntax for using theme from sx prop is to declare a parameter, not object pattern ({ theme })

Expected behavior

The sx prop should work with sx={theme => …}

Context

I am working on a codemod for transforming sx prop and I stumbled on this error.

Your environment

npx @mui/envinfo
  Don't forget to mention which browser you used.
  Output from `npx @mui/envinfo` goes here.

Search keywords: sx theme

Support array in `sx` prop

Summary

The sx prop should support array format to cover these cases:

  • array of styles

    // developer's codebase
    <div
      sx={[
        { display: 'flex',  },
        numSelected > 0 && {
           bgcolor: 'white',
         },
      ]}
    />
    
    // transformed
    <div {...sx(['s1o8xp19', 's1o8xp19-1'], {})} />
  • array of function

     // developer's codebase
     <div
       sx={[
         theme => ({ display: 'flex', fontSize: theme.vars.font.xl }),
         numSelected > 0 && (theme => {
            bgcolor: theme.vars.palette.primary.main,
            …theme.applyStyles('dark', {
              bgcolor: theme.vars.palette.primary.light,
            })
          }),
       ]}
     />
    
     // transformed
     <div {...sx(['s1xbsywq', numSelected > 0 && 's1xbsywq-0'], {})} />

Examples

No response

Motivation

To ease v5 to v6 migration and ensure feature parity with the current MUI system's sx.

Search keywords: array sx prop

[sx] support function declaration

Summary

A framework like Next.js transpile the code from ArrowFunction to FunctionDeclaration that developers don't have control:

 ⨯ unhandledRejection: SyntaxError: /Users/siriwatknp/practice-repos/nextjs-tailwind-v4/src/app/landing-page/components/Features.tsx: @pigment-css/react: sx prop only supports arrow functions directly returning an object, for example () => ({color: 'red'}). You can accept theme object in the params if required.
  171 |                             children: [
  172 |                                 /*#__PURE__*/ _jsxDEV(Box, {
> 173 |                                     sx: (param)=>{
      |                                                  ^
  174 |                                         let { theme } = param;
  175 |                                         return {
  176 |                                             backgroundSize: "cover",

The original code is:

<Box
  sx={({ theme }) => ({
    backgroundSize: 'cover',
    backgroundPosition: 'center',
    minHeight: 280,
    backgroundImage: 'var(--Image-light)',
    ...theme.applyStyles('dark', {
       backgroundImage: 'var(--Image-dark)',
    }),
  })}

The sx prop should work with function declaration too.

Examples

No response

Motivation

No response

Search keywords: sx function declaration

[bug] Theme injection error in Vite without a theme

Steps to reproduce

When you add a pigment to a new Vite project, following the instructions in the README.md, you get the following error on build:

TypeError: [vite-mui-theme-injection-plugin] Could not load �zero-runtime-styles.css (imported by src/app/main.jsx): Cannot read properties of undefined (reading 'generateStyleSheets')

Turns out the plugin blows up right now unless you add a theme into the config:

// This fails
export default defineConfig({
  plugins: [
    pigment({}),
    react()
  ]
});

// This works
export default defineConfig({
  plugins: [
    pigment({
      theme: {}
    }),
    react()
  ]
});

I'm not entirely sure if this is a documentation issue or an actual bug, but I feel like it's nice to not require a project to always supply a theme.

Steps:

  • Create a new Vite project using: yarn create vite my-vue-app --template react
  • Add pigment as detailed in the README
  • Run yarn build

Repro available in this codespace: https://musical-guacamole-45qr7r77xq2jpg5.github.dev/

Current behavior

Following the README's instructions for a Vite project, you will get a build error instead of a cleanly built project.

Expected behavior

Ideally not including a template doesn't throw an error at build time. Otherwise the README should specify that a theme is required

Context

No response

Your environment

npx @mui/envinfo
  System:
    OS: macOS 14.2.1
  Binaries:
    Node: 21.7.3 - ~/.nvm/versions/node/v21.7.3/bin/node
    Yarn: 4.1.1
    pnpm: Not Found
  Browsers:
    Chrome: 124.0.6367.119
    Safari: 17.2.1
  npmPackages:
    @pigment-css/react: ^0.0.10
    @vite: ^5.2.0
    @emotion/react: latest => 11.11.4
    @emotion/styled: latest => 11.11.5
    @mui/base:  5.0.0-beta.43
    @mui/core-downloads-tracker:  6.0.0-dev.240424162023-9968b4889d
    @mui/material: next => 6.0.0-alpha.5
    @mui/private-theming:  6.0.0-alpha.5
    @mui/styled-engine:  6.0.0-alpha.5
    @mui/system:  6.0.0-dev.240424162023-9968b4889d
    @mui/types:  7.2.14
    @mui/utils:  6.0.0-alpha.5
    @types/react: ^18.2.66 => 18.3.1
    react: ^18.2.0 => 18.3.1
    react-dom: ^18.2.0 => 18.3.1
    typescript:  5.4.5

Search keywords: theme pigment

[vite] Missing common `babel` plugin(s)

Steps to reproduce

  1. Use barrel files import * as something from "example.ts"
    • it's easier to reproduce with Storybook, as it just doesn't start in Storybook, even if barrel files are not related to files where pigment is used
  2. See bug in CLI related to babel's transformation of namespaces

Current behavior

Currently, you must pass to the vite plugin:

pigment({
  babelOptions: {
    plugins: [`@babel/plugin-transform-export-namespace-from`],
  },
})

Expected behavior

The babel plugin @babel/plugin-transform-export-namespace-from should be added by default.
In:
https://github.com/mui/material-ui/blob/f3cb496c999acbc8f19533e38df20be12e56d059/packages/pigment-css-vite-plugin/src/vite-plugin.ts#L194

Context

Related issues:

Your environment

npx @mui/envinfo

  System:
    OS: macOS 14.4
  Binaries:
    Node: 21.7.1 - /opt/homebrew/bin/node
    npm: 10.5.0 - /opt/homebrew/bin/npm
    pnpm: 8.15.5 - /opt/homebrew/bin/pnpm
  Browsers:
    Chrome: 123.0.6312.87
    Edge: 112.0.1722.39
    Safari: 17.4
  npmPackages:
    @pigment-css/react: 0.0.5
    @pigment-css/vite-plugin: 0.0.5
    @emotion/react:  11.11.4 
    @emotion/styled:  11.11.5 
    @mui/styled-engine:  6.0.0-alpha.1
    @mui/system:  6.0.0-alpha.1
    @mui/types:  7.2.14 
    @types/react: 18.2.73 => 18.2.73 
    react: ^18.2.0 => 18.2.0 
    react-dom: ^18.2.0 => 18.2.0 
    typescript: 5.4.3 => 5.4.3 

Search keywords: pigment barrel

React 19. Generate a component to avoid FOUC

Summary

React 19 adds support for loading stylesheets in a specified order AND ability to wait using Suspense wrapper until CSS is loaded.

Please consider to:

  • generate a component wrapper to at least the main css bundle and ideally wrap dynamically imported pages with a separate Suspense with a list of CSS files that would be imported in the dynamically imported page

Examples

Motivation

Disable FOUC when needed

Search keywords: react 19 fouc suspense

[core] Bug when trying to wrap the `styled` function

Steps to reproduce

Links to live example:
Next.JS: https://stackblitz.com/edit/stackblitz-starters-e6kyr6?description=The%20React%20framework%20for%20production&file=app%2Fpage.tsx&title=Next.js%20Starter
Vite: https://stackblitz.com/edit/vitejs-vite-14nhvq?file=src%2FApp.tsx

Steps: just click the link and let the app start.

Current behavior

There appears to be a bug when trying to wrap styled function from "@pigment-css/react".

Here's the screenshot from Vite environment
image

Expected behavior

The CSS should be applied to the element.

Context

I am trying to have a simple wrapper for styled function from "@pigment-css/react".

Since it doesn't work, I'm wondering whether is it a bug or is it simply not supported?

Your environment

npx @mui/envinfo
  Don't forget to mention which browser you used.
  Output from `npx @mui/envinfo` goes here.

Search keywords: pigment-css wrapper

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.