react-strict-dom icon indicating copy to clipboard operation
react-strict-dom copied to clipboard

Cannot specify media query in different module - Invalid pseudo or at-rule.

Open lotusjohn opened this issue 1 year ago • 6 comments

Describe the issue

Im getting an error when I try to specify a helper for media queries to not have to write the string in every condition (or a const in every module I want to use it) Invalid pseudo or at-rule. If I specify the const in the same module, it works. It doesn't matter if it is in a .stylex.ts file or .ts only.

// media.stylex.ts or media.ts
export const MEDIA = {
  md: "@media (min-width: 1024px)",
  lg: "@media (min-width: 1920px)",
};
//Component.tsx
const styles = css.create({
  comp: {
    width: {
      default: 100,
      [MEDIA.md]: 200
    }
  }
})

export const Comp = () => {
   return <html.div style={styles.comp} />
}

Expected behavior

Expect to be able to import media query helper from a different module.

Steps to reproduce

Basic setup.

  1. Create a basic RSD install
  2. Create a media.stylex.ts file
  3. Add this block to it
export const MEDIA = {
  md: "@media (min-width: 1024px)",
  lg: "@media (min-width: 1920px)",
};
  1. Create a Comp.tsx file
  2. Add a component using this media query and import the MEDIA const from the other module
const styles = css.create({
  comp: {
    width: {
      default: 100,
      [MEDIA.md]: 200
    }
  }
})

export const Comp = () => {
   return <html.div style={styles.comp}>Hello</html.div>
}

Test case

No response

Additional comments

strict-dom version 0.0.27

lotusjohn avatar Nov 28 '24 08:11 lotusjohn

This is a shortcoming of StyleX. See https://github.com/facebook/stylex/issues/724

necolas avatar Nov 28 '24 10:11 necolas

Any workarounds or suggestions for code splitting?

LunatiqueCoder avatar Apr 06 '25 20:04 LunatiqueCoder

@LunatiqueCoder The easiest workaround is to repeat the media queries in every file where you need to use them and use Typescript types to enforce that they remain the same across files.

export type TSM = '@media (max-width: 768px)';

/// other file

const SM: TSM = '@media (max-width: 768px)';

What do you mean when saying "code splitting"?

nmn avatar Apr 09 '25 05:04 nmn

@nmn oh your solution should work pretty good. Thank you for the suggestion!

LunatiqueCoder avatar Apr 09 '25 05:04 LunatiqueCoder

Here's my take:

1. Declare your breakpoints like this:

breakpoints.stylex.ts:

export const BREAKPOINTS = {
  xl: 1650,
  lg: 1280,
  md: 1020,
  sm: 800,
  xs: 660,
  xxs: 390,
  gtXl: 1651,
  gtLg: 1281,
  gtMd: 1021,
  gtSm: 801,
  gtXs: 661,
  gtXxs: 391,
} as const;

export const MEDIA_QUERIES = {
  // Max-width queries
  xl: `(max-width: ${BREAKPOINTS.xl}px)`,
  lg: `(max-width: ${BREAKPOINTS.lg}px)`,
  md: `(max-width: ${BREAKPOINTS.md}px)`,
  sm: `(max-width: ${BREAKPOINTS.sm}px)`,
  xs: `(max-width: ${BREAKPOINTS.xs}px)`,
  xxs: `(max-width: ${BREAKPOINTS.xxs}px)`,

  // Min-width queries (GT = greater than)
  gtXl: `(min-width: ${BREAKPOINTS.gtXl}px)`,
  gtLg: `(min-width: ${BREAKPOINTS.gtLg}px)`,
  gtMd: `(min-width: ${BREAKPOINTS.gtMd}px)`,
  gtSm: `(min-width: ${BREAKPOINTS.gtSm}px)`,
  gtXs: `(min-width: ${BREAKPOINTS.gtXs}px)`,
  gtXxs: `(min-width: ${BREAKPOINTS.gtXxs}px)`,

  // Other media queries
  short: '(max-height: 820px)',
  tall: '(min-height: 820px)',

  hoverNone: '(hover: none)',
  pointerCoarse: '(pointer: coarse)',
  pointerFine: '(pointer: fine)',
} as const;

// Helper to pick specific media queries when needed
export type TPickMediaQuery<TPick extends keyof typeof MEDIA_QUERIES> = {
  [key in TPick]: `@media ${(typeof MEDIA_QUERIES)[key]}`;
};


2. Then you can use it like this:

import { TPickMediaQuery } from '@/src/theme/breakpoints.stylex';

const MEDIA_QUERY: TPickMediaQuery<'xl' | 'xs'> = { // Pick your needed breakpoints here. TS will help you autocomplete the values
  xl: '@media (max-width: 1650px)',
  xs: '@media (max-width: 660px)',
};

export const styles = css.create({
  headerImage: {
    height: {
      default: '300px',
      [MEDIA_QUERY.xs]: '203px',
    },
  },
  image: {
    width: {
      default: '525px',
      [MEDIA_QUERY.xs]: '273px',
    },
    height: {
      default: '391px',
      [MEDIA_QUERY.xs]: '203px',
    },
  },
}


3. BONUS: Use the breakpoints with @expo/match-media and react-responsive:

useMedia.ts:


import { useMediaQuery } from 'react-responsive';

import { MEDIA_QUERIES } from '../theme/breakpoints.stylex';

export const useMedia = () => {
  const xl = useMediaQuery({ query: MEDIA_QUERIES.xl });
  const lg = useMediaQuery({ query: MEDIA_QUERIES.lg });
  const md = useMediaQuery({ query: MEDIA_QUERIES.md });
  const sm = useMediaQuery({ query: MEDIA_QUERIES.sm });
  const xs = useMediaQuery({ query: MEDIA_QUERIES.xs });
  const xxs = useMediaQuery({ query: MEDIA_QUERIES.xxs });

  const gtXs = useMediaQuery({ query: MEDIA_QUERIES.gtXs });
  const gtSm = useMediaQuery({ query: MEDIA_QUERIES.gtSm });
  const gtMd = useMediaQuery({ query: MEDIA_QUERIES.gtMd });
  const gtLg = useMediaQuery({ query: MEDIA_QUERIES.gtLg });
  const gtXl = useMediaQuery({ query: MEDIA_QUERIES.gtXl });

  const short = useMediaQuery({ query: MEDIA_QUERIES.short });
  const tall = useMediaQuery({ query: MEDIA_QUERIES.tall });

  const hoverNone = useMediaQuery({ query: MEDIA_QUERIES.hoverNone });
  const pointerCoarse = useMediaQuery({ query: MEDIA_QUERIES.pointerCoarse });
  const pointerFine = useMediaQuery({ query: MEDIA_QUERIES.pointerFine });

  return {
    xl,
    lg,
    md,
    sm,
    xs,
    xxs,
    gtXs,
    gtSm,
    gtMd,
    gtLg,
    gtXl,
    short,
    tall,
    hoverNone,
    pointerCoarse,
    pointerFine,
  };
};

LunatiqueCoder avatar Apr 27 '25 06:04 LunatiqueCoder

I would generally recommend against using useMatchMedia on the web as it breaks SSR. Outside of that, cool trick with Typescript.

We're working on a feature that'll make it possible to actually share media queries soon.

nmn avatar Apr 28 '25 04:04 nmn