diff --git a/apps/pigment-css-next-app/src/app/hidden/page.tsx b/apps/pigment-css-next-app/src/app/hidden/page.tsx
new file mode 100644
index 00000000..84dbeeed
--- /dev/null
+++ b/apps/pigment-css-next-app/src/app/hidden/page.tsx
@@ -0,0 +1,20 @@
+import Hidden from '@pigment-css/react/Hidden';
+
+export default function HiddenDemo() {
+ return (
+
+
+ Hidden on small screens and down
+
+
+ Hidden on medium screens and up
+
+
+ Hidden on sm
+
+
+ Hidden on md and xl
+
+
+ );
+}
diff --git a/packages/pigment-css-react/package.json b/packages/pigment-css-react/package.json
index 7aa5401d..cf84dea8 100644
--- a/packages/pigment-css-react/package.json
+++ b/packages/pigment-css-react/package.json
@@ -170,6 +170,15 @@
},
"require": "./build/Container.js",
"default": "./build/Container.js"
+ },
+ "./Hidden": {
+ "types": "./build/Hidden.d.ts",
+ "import": {
+ "types": "./build/Hidden.d.mts",
+ "default": "./build/Hidden.mjs"
+ },
+ "require": "./build/Hidden.js",
+ "default": "./build/Hidden.js"
}
},
"nx": {
diff --git a/packages/pigment-css-react/src/Hidden.d.ts b/packages/pigment-css-react/src/Hidden.d.ts
new file mode 100644
index 00000000..7305ce44
--- /dev/null
+++ b/packages/pigment-css-react/src/Hidden.d.ts
@@ -0,0 +1,18 @@
+import { Breakpoint } from './base';
+import { PolymorphicComponent } from './Box';
+
+type HiddenUp = {
+ [key in Breakpoint as `${key}Up`]?: boolean;
+};
+type HiddenDown = {
+ [key in Breakpoint as `${key}Down`]?: boolean;
+};
+
+interface HiddenBaseProps extends HiddenUp, HiddenDown {
+ className?: string;
+ only?: Breakpoint | Breakpoint[];
+}
+
+declare const Hidden: PolymorphicComponent;
+
+export default Hidden;
diff --git a/packages/pigment-css-react/src/Hidden.jsx b/packages/pigment-css-react/src/Hidden.jsx
new file mode 100644
index 00000000..8ca1ebb4
--- /dev/null
+++ b/packages/pigment-css-react/src/Hidden.jsx
@@ -0,0 +1,128 @@
+/* eslint-disable react/jsx-filename-extension */
+import * as React from 'react';
+import clsx from 'clsx';
+import PropTypes from 'prop-types';
+import { generateAtomics } from './generateAtomics';
+
+const hiddenAtomics = generateAtomics(({ theme }) => {
+ const conditions = {};
+
+ for (let i = 0; i < theme.breakpoints.keys.length; i += 1) {
+ const breakpoint = theme.breakpoints.keys[i];
+ conditions[`${theme.breakpoints.keys[i]}Only`] = theme.breakpoints.only(breakpoint);
+ conditions[`${theme.breakpoints.keys[i]}Up`] = theme.breakpoints.up(breakpoint);
+ conditions[`${theme.breakpoints.keys[i]}Down`] = theme.breakpoints.down(breakpoint);
+ }
+
+ return {
+ conditions,
+ properties: {
+ display: ['none'],
+ },
+ };
+});
+
+const Hidden = React.forwardRef(function Hidden(
+ { className, component = 'div', style, ...props },
+ ref,
+) {
+ const rest = {};
+ const breakpointProps = {};
+ Object.keys(props).forEach((key) => {
+ if (key.endsWith('Up') || key.endsWith('Down')) {
+ breakpointProps[key] = 'none';
+ } else if (key === 'only') {
+ if (typeof props[key] === 'string') {
+ breakpointProps[`${props[key]}Only`] = 'none';
+ }
+ if (Array.isArray(props[key])) {
+ props[key].forEach((val) => {
+ breakpointProps[`${val}Only`] = 'none';
+ });
+ }
+ } else {
+ rest[key] = props[key];
+ }
+ });
+ const stackClasses = hiddenAtomics({ display: breakpointProps });
+ const Component = component;
+ return (
+
+ );
+});
+
+if (process.env.NODE_ENV !== 'production') {
+ Hidden.propTypes = {
+ /**
+ * The content of the component.
+ */
+ children: PropTypes.node,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
+ /**
+ * The component used for the root node.
+ * Either a string to use a HTML element or a component.
+ */
+ component: PropTypes.elementType,
+ /**
+ * If `true`, screens this size and down are hidden.
+ */
+ lgDown: PropTypes.bool,
+ /**
+ * If `true`, screens this size and up are hidden.
+ */
+ lgUp: PropTypes.bool,
+ /**
+ * If `true`, screens this size and down are hidden.
+ */
+ mdDown: PropTypes.bool,
+ /**
+ * If `true`, screens this size and up are hidden.
+ */
+ mdUp: PropTypes.bool,
+ /**
+ * Hide the given breakpoint(s).
+ */
+ only: PropTypes.oneOfType([
+ PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
+ PropTypes.arrayOf(PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl'])),
+ ]),
+ /**
+ * If `true`, screens this size and down are hidden.
+ */
+ smDown: PropTypes.bool,
+ /**
+ * If `true`, screens this size and up are hidden.
+ */
+ smUp: PropTypes.bool,
+ /**
+ * @ignore
+ */
+ style: PropTypes.object,
+ /**
+ * If `true`, screens this size and down are hidden.
+ */
+ xlDown: PropTypes.bool,
+ /**
+ * If `true`, screens this size and up are hidden.
+ */
+ xlUp: PropTypes.bool,
+ /**
+ * If `true`, screens this size and down are hidden.
+ */
+ xsDown: PropTypes.bool,
+ /**
+ * If `true`, screens this size and up are hidden.
+ */
+ xsUp: PropTypes.bool,
+ };
+}
+
+export default Hidden;
diff --git a/packages/pigment-css-react/src/generateAtomics.js b/packages/pigment-css-react/src/generateAtomics.js
index 7b0c2fdc..927f389b 100644
--- a/packages/pigment-css-react/src/generateAtomics.js
+++ b/packages/pigment-css-react/src/generateAtomics.js
@@ -43,11 +43,19 @@ export function atomics({ styles, shorthands, conditions, defaultCondition, mult
inlineStyle[`${key}${breakpoint === defaultCondition ? '' : `-${breakpoint}`}`] =
styleValue;
} else {
- classes.push(styleClasses[value][breakpoint]);
+ classes.push(
+ typeof styleClasses[value] !== 'object'
+ ? styleClasses[value]
+ : styleClasses[value][breakpoint],
+ );
}
}
- if (typeof propertyValue === 'string' || typeof propertyValue === 'number') {
+ if (
+ typeof propertyValue === 'string' ||
+ typeof propertyValue === 'number' ||
+ typeof propertyValue === 'boolean'
+ ) {
handlePrimitive(propertyValue);
} else if (Array.isArray(propertyValue)) {
propertyValue.forEach((value, index) => {
@@ -78,7 +86,7 @@ export function atomics({ styles, shorthands, conditions, defaultCondition, mult
const inlineStyle = {};
Object.keys(props).forEach((cssProperty) => {
const values = props[cssProperty];
- if (cssProperty in shorthands) {
+ if (shorthands && cssProperty in shorthands) {
const configShorthands = shorthands[cssProperty];
if (!configShorthands) {
return;
diff --git a/packages/pigment-css-react/src/index.ts b/packages/pigment-css-react/src/index.ts
index 149fac4b..f228f6ed 100644
--- a/packages/pigment-css-react/src/index.ts
+++ b/packages/pigment-css-react/src/index.ts
@@ -6,5 +6,4 @@ export { generateAtomics, atomics } from './generateAtomics';
export { default as css } from './css';
export { default as createUseThemeProps } from './createUseThemeProps';
export { default as internal_createExtendSxProp } from './createExtendSxProp';
-export { default as Box } from './Box';
export { default as useTheme } from './useTheme';
diff --git a/packages/pigment-css-react/src/utils/valueToLiteral.test.ts b/packages/pigment-css-react/src/utils/valueToLiteral.test.ts
new file mode 100644
index 00000000..6174813e
--- /dev/null
+++ b/packages/pigment-css-react/src/utils/valueToLiteral.test.ts
@@ -0,0 +1,53 @@
+import { expect } from 'chai';
+import { valueToLiteral } from './valueToLiteral';
+
+describe('valueToLiteral', () => {
+ it('should work with undefined as a value', () => {
+ expect(
+ valueToLiteral({
+ foo: undefined,
+ }),
+ ).to.deep.equal({
+ type: 'ObjectExpression',
+ properties: [
+ {
+ type: 'ObjectProperty',
+ computed: false,
+ shorthand: false,
+ key: {
+ type: 'Identifier',
+ name: 'foo',
+ },
+ value: {
+ type: 'Identifier',
+ name: 'undefined',
+ },
+ },
+ ],
+ });
+ });
+
+ it('should work with null as a value', () => {
+ expect(
+ valueToLiteral({
+ foo: null,
+ }),
+ ).to.deep.equal({
+ type: 'ObjectExpression',
+ properties: [
+ {
+ type: 'ObjectProperty',
+ computed: false,
+ shorthand: false,
+ key: {
+ type: 'Identifier',
+ name: 'foo',
+ },
+ value: {
+ type: 'NullLiteral',
+ },
+ },
+ ],
+ });
+ });
+});
diff --git a/packages/pigment-css-react/src/utils/valueToLiteral.ts b/packages/pigment-css-react/src/utils/valueToLiteral.ts
index 8f737b17..2081b4ab 100644
--- a/packages/pigment-css-react/src/utils/valueToLiteral.ts
+++ b/packages/pigment-css-react/src/utils/valueToLiteral.ts
@@ -8,7 +8,7 @@ export function isSerializable(o: unknown): o is Serializable {
return o.every(isSerializable);
}
- if (o === null) {
+ if (o === null || o === undefined) {
return true;
}
diff --git a/packages/pigment-css-react/tests/Hidden/Hidden.test.js b/packages/pigment-css-react/tests/Hidden/Hidden.test.js
new file mode 100644
index 00000000..468b459f
--- /dev/null
+++ b/packages/pigment-css-react/tests/Hidden/Hidden.test.js
@@ -0,0 +1,33 @@
+import * as React from 'react';
+import path from 'node:path';
+import { createRenderer } from '@mui/internal-test-utils';
+import { createBreakpoints } from '@mui/system';
+import { runTransformation, expect } from '../testUtils';
+
+describe('Pigment CSS - Hidden', () => {
+ const { render } = createRenderer();
+
+ it('should transform and render sx prop', async () => {
+ const { output, fixture } = await runTransformation(
+ path.join(__dirname, '../../src/Hidden.jsx'),
+ {
+ themeArgs: {
+ theme: {
+ breakpoints: createBreakpoints({}),
+ },
+ },
+ outputDir: path.join(__dirname, 'fixtures'),
+ },
+ );
+
+ expect(output.js).to.equal(fixture.js);
+ expect(output.css).to.equal(fixture.css);
+
+ const HiddenOutput = (await import('./fixtures/Hidden.output')).default;
+
+ const { container } = render();
+ const classNames = new Set([...container.firstChild.className.split(' ')]);
+
+ expect(classNames.size).to.equal(4);
+ });
+});
diff --git a/packages/pigment-css-react/tests/Hidden/fixtures/Hidden.output.css b/packages/pigment-css-react/tests/Hidden/fixtures/Hidden.output.css
new file mode 100644
index 00000000..adaf0873
--- /dev/null
+++ b/packages/pigment-css-react/tests/Hidden/fixtures/Hidden.output.css
@@ -0,0 +1,75 @@
+@media (min-width: 0px) and (max-width: 599.95px) {
+ .hccfrvp1 {
+ display: none;
+ }
+}
+@media (min-width: 0px) {
+ .hccfrvp2 {
+ display: none;
+ }
+}
+@media (max-width: -0.05px) {
+ .hccfrvp3 {
+ display: none;
+ }
+}
+@media (min-width: 600px) and (max-width: 899.95px) {
+ .hccfrvp4 {
+ display: none;
+ }
+}
+@media (min-width: 600px) {
+ .hccfrvp5 {
+ display: none;
+ }
+}
+@media (max-width: 599.95px) {
+ .hccfrvp6 {
+ display: none;
+ }
+}
+@media (min-width: 900px) and (max-width: 1199.95px) {
+ .hccfrvp7 {
+ display: none;
+ }
+}
+@media (min-width: 900px) {
+ .hccfrvp8 {
+ display: none;
+ }
+}
+@media (max-width: 899.95px) {
+ .hccfrvp9 {
+ display: none;
+ }
+}
+@media (min-width: 1200px) and (max-width: 1535.95px) {
+ .hccfrvp10 {
+ display: none;
+ }
+}
+@media (min-width: 1200px) {
+ .hccfrvp11 {
+ display: none;
+ }
+}
+@media (max-width: 1199.95px) {
+ .hccfrvp12 {
+ display: none;
+ }
+}
+@media (min-width: 1536px) {
+ .hccfrvp13 {
+ display: none;
+ }
+}
+@media (min-width: 1536px) {
+ .hccfrvp14 {
+ display: none;
+ }
+}
+@media (max-width: 1535.95px) {
+ .hccfrvp15 {
+ display: none;
+ }
+}
diff --git a/packages/pigment-css-react/tests/Hidden/fixtures/Hidden.output.js b/packages/pigment-css-react/tests/Hidden/fixtures/Hidden.output.js
new file mode 100644
index 00000000..18c20b17
--- /dev/null
+++ b/packages/pigment-css-react/tests/Hidden/fixtures/Hidden.output.js
@@ -0,0 +1,155 @@
+import { atomics as _atomics } from '@pigment-css/react';
+/* eslint-disable react/jsx-filename-extension */
+import * as React from 'react';
+import clsx from 'clsx';
+import PropTypes from 'prop-types';
+const hiddenAtomics = /*#__PURE__*/ _atomics({
+ styles: {
+ display: {
+ none: {
+ xsOnly: 'hccfrvp1',
+ xsUp: 'hccfrvp2',
+ xsDown: 'hccfrvp3',
+ smOnly: 'hccfrvp4',
+ smUp: 'hccfrvp5',
+ smDown: 'hccfrvp6',
+ mdOnly: 'hccfrvp7',
+ mdUp: 'hccfrvp8',
+ mdDown: 'hccfrvp9',
+ lgOnly: 'hccfrvp10',
+ lgUp: 'hccfrvp11',
+ lgDown: 'hccfrvp12',
+ xlOnly: 'hccfrvp13',
+ xlUp: 'hccfrvp14',
+ xlDown: 'hccfrvp15',
+ },
+ },
+ },
+ shorthands: {},
+ conditions: [
+ 'xsOnly',
+ 'xsUp',
+ 'xsDown',
+ 'smOnly',
+ 'smUp',
+ 'smDown',
+ 'mdOnly',
+ 'mdUp',
+ 'mdDown',
+ 'lgOnly',
+ 'lgUp',
+ 'lgDown',
+ 'xlOnly',
+ 'xlUp',
+ 'xlDown',
+ ],
+ defaultCondition: undefined,
+ multiplier: undefined,
+});
+const Hidden = React.forwardRef(function Hidden(
+ { className, component = 'div', style, ...props },
+ ref,
+) {
+ const rest = {};
+ const breakpointProps = {};
+ Object.keys(props).forEach((key) => {
+ if (key.endsWith('Up') || key.endsWith('Down')) {
+ breakpointProps[key] = 'none';
+ } else if (key === 'only') {
+ if (typeof props[key] === 'string') {
+ breakpointProps[`${props[key]}Only`] = 'none';
+ }
+ if (Array.isArray(props[key])) {
+ props[key].forEach((val) => {
+ breakpointProps[`${val}Only`] = 'none';
+ });
+ }
+ } else {
+ rest[key] = props[key];
+ }
+ });
+ const stackClasses = hiddenAtomics({
+ display: breakpointProps,
+ });
+ const Component = component;
+ return (
+
+ );
+});
+if (process.env.NODE_ENV !== 'production') {
+ Hidden.propTypes = {
+ /**
+ * The content of the component.
+ */
+ children: PropTypes.node,
+ /**
+ * @ignore
+ */
+ className: PropTypes.string,
+ /**
+ * The component used for the root node.
+ * Either a string to use a HTML element or a component.
+ */
+ component: PropTypes.elementType,
+ /**
+ * If `true`, screens this size and down are hidden.
+ */
+ lgDown: PropTypes.bool,
+ /**
+ * If `true`, screens this size and up are hidden.
+ */
+ lgUp: PropTypes.bool,
+ /**
+ * If `true`, screens this size and down are hidden.
+ */
+ mdDown: PropTypes.bool,
+ /**
+ * If `true`, screens this size and up are hidden.
+ */
+ mdUp: PropTypes.bool,
+ /**
+ * Hide the given breakpoint(s).
+ */
+ only: PropTypes.oneOfType([
+ PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
+ PropTypes.arrayOf(PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl'])),
+ ]),
+ /**
+ * If `true`, screens this size and down are hidden.
+ */
+ smDown: PropTypes.bool,
+ /**
+ * If `true`, screens this size and up are hidden.
+ */
+ smUp: PropTypes.bool,
+ /**
+ * @ignore
+ */
+ style: PropTypes.object,
+ /**
+ * If `true`, screens this size and down are hidden.
+ */
+ xlDown: PropTypes.bool,
+ /**
+ * If `true`, screens this size and up are hidden.
+ */
+ xlUp: PropTypes.bool,
+ /**
+ * If `true`, screens this size and down are hidden.
+ */
+ xsDown: PropTypes.bool,
+ /**
+ * If `true`, screens this size and up are hidden.
+ */
+ xsUp: PropTypes.bool,
+ };
+}
+export default Hidden;
diff --git a/packages/pigment-css-react/tests/generateAtomics.test.js b/packages/pigment-css-react/tests/generateAtomics.test.js
index 940f108f..7fd9ae9d 100644
--- a/packages/pigment-css-react/tests/generateAtomics.test.js
+++ b/packages/pigment-css-react/tests/generateAtomics.test.js
@@ -149,4 +149,88 @@ describe('generateAtomics', () => {
style: {},
});
});
+
+ describe('hidden atomics', () => {
+ const hiddenAtomic = atomics({
+ styles: {
+ display: {
+ none: {
+ xsUp: 'display-none-xsUp',
+ xsDown: 'display-none-xsDown',
+ smUp: 'display-none-smUp',
+ smDown: 'display-none-smDown',
+ mdUp: 'display-none-mdUp',
+ mdDown: 'display-none-mdDown',
+ lgUp: 'display-none-lgUp',
+ lgDown: 'display-none-lgDown',
+ xlUp: 'display-none-xlUp',
+ xlDown: 'display-none-xlDown',
+ xsOnly: 'display-none-onlyXs',
+ smOnly: 'display-none-onlySm',
+ mdOnly: 'display-none-onlyMd',
+ lgOnly: 'display-none-onlyLg',
+ xlOnly: 'display-none-onlyXl',
+ },
+ },
+ },
+ conditions: [
+ 'xsUp',
+ 'xsDown',
+ 'smUp',
+ 'smDown',
+ 'mdUp',
+ 'mdDown',
+ 'lgUp',
+ 'lgDown',
+ 'xlUp',
+ 'xlDown',
+ 'xsOnly',
+ 'smOnly',
+ 'mdOnly',
+ 'lgOnly',
+ 'xlOnly',
+ ],
+ });
+
+ it('should generate up and down classes', () => {
+ expect(
+ hiddenAtomic({
+ display: {
+ smDown: 'none',
+ lgUp: 'none',
+ },
+ }),
+ ).to.deep.equal({
+ className: 'display-none-smDown display-none-lgUp',
+ style: {},
+ });
+ });
+
+ it('should work with only sm', () => {
+ expect(
+ hiddenAtomic({
+ display: {
+ smOnly: 'none',
+ },
+ }),
+ ).to.deep.equal({
+ className: 'display-none-onlySm',
+ style: {},
+ });
+ });
+
+ it('should work with only with array value', () => {
+ expect(
+ hiddenAtomic({
+ display: {
+ smOnly: 'none',
+ lgOnly: 'none',
+ },
+ }),
+ ).to.deep.equal({
+ className: 'display-none-onlySm display-none-onlyLg',
+ style: {},
+ });
+ });
+ });
});
diff --git a/packages/pigment-css-react/tests/testUtils.ts b/packages/pigment-css-react/tests/testUtils.ts
index f5577b80..12022553 100644
--- a/packages/pigment-css-react/tests/testUtils.ts
+++ b/packages/pigment-css-react/tests/testUtils.ts
@@ -112,11 +112,13 @@ export async function runTransformation(absolutePath: string, options?: Transfor
});
if (!outputContent || shouldUpdateOutput) {
+ fs.mkdirSync(path.dirname(outputFilePath), { recursive: true });
fs.writeFileSync(outputFilePath, formattedJs, 'utf-8');
outputContent = formattedJs;
}
if (!outputCssContent || shouldUpdateOutput) {
+ fs.mkdirSync(path.dirname(outputCssFilePath), { recursive: true });
fs.writeFileSync(outputCssFilePath, formattedCss, 'utf-8');
outputCssContent = formattedCss;
}
diff --git a/packages/pigment-css-react/tsup.config.ts b/packages/pigment-css-react/tsup.config.ts
index 84ec3172..1bab5658 100644
--- a/packages/pigment-css-react/tsup.config.ts
+++ b/packages/pigment-css-react/tsup.config.ts
@@ -27,6 +27,7 @@ const BASE_FILES = [
'RtlProvider.tsx',
'Stack.jsx',
'Container.jsx',
+ 'Hidden.jsx',
];
export default defineConfig([