examunity / bootstrap-rn

Bootstrap components for React Native
MIT License
2 stars 0 forks source link

New Media component #39

Open markusjwetzel opened 6 months ago

markusjwetzel commented 6 months ago

Currently we pass a ssrViewport value to the provider for guessing the viewport on server-side. This is error-prone and it would be better to remove the ssrViewport prop and also the useMedia() hook in favour of a new Media component.

The Media component should work similar to https://github.com/artsy/fresnel:

<Media up="md">
  <Text>Desktop</Text>
</Media>
<Media down="sm">
  <Text>Mobile</Text>
</Media>

Instead of determine the viewport programmatically, css media queries should be used on the web. This is not only necessary for a new Media component, but also for all other styles. It might also be benefitial to compile the full styles to css, so that also the css pseudo classes like :hover are used.

For inline styles there should be no support for media queries and pseudo classes.

On native we should still determine viewports and pseudo classes programmatically.

markusjwetzel commented 6 months ago

Helpful packages for supporting css media queries and pseudo classes:

Also calling StyleSheet(styles) returns [classNames, inlineStyles], so we can get the class names of the atomic styles. It might be possible to modify the class names.

markusjwetzel commented 6 months ago

A possible solution would be to fork the StyleSheet export of react-native-web and create one style sheet per media query:

import createCSSStyleSheet from 'react-native-web/dist/exports/StyleSheet/dom/createCSSStyleSheet';
import createOrderedCSSStyleSheet from 'react-native-web/dist/exports/StyleSheet/dom/createOrderedCSSStyleSheet';

const prefix = 'react-native-stylesheet';
const sheets = {};

function createSheet(mediaQuery) {
  if (!sheets[mediaQuery]) {
    const id = `${prefix}-${mediaQuery}`;
    sheets[mediaQuery] = createOrderedCSSStyleSheet(createCSSStyleSheet(id));

    // Make sure sheet is for media query only
    document.getElementById(id).setAttribute('media', mediaQuery);
  }

  const sheet = sheets[mediaQuery];

  return {
    getTextContent() {
      return sheet.getTextContent();
    },
    id,
    insert(cssText, groupValue) {
      sheet.insert(cssText, groupValue);
    }
  }
}

The styles can be inserted similar to the implementation on StyleSheet.js:

import { atomic } from 'react-native-web/dist/exports/StyleSheet/compiler';

function customStyleq(styles, options: Options = {}) {
  // TODO
}

function insertRules(mediaQuery, compiledOrderedRules) {
  compiledOrderedRules.forEach(([rules, order]) => {
    rules.forEach((rule) => {
      sheet[mediaQuery].insert(rule, order);
    });
  });
}

function compileAndInsertAtomic(mediaQuery, style) {
  const [compiledStyle, compiledOrderedRules] = atomic(
    preprocess(style, defaultPreprocessOptions)
  );

  // TODO: Prefix class names by media query hash, e.g. rule.replace('.', `.${hash(mediaQuery)}-`);

  insertRules(mediaQuery, compiledOrderedRules);
  return compiledStyle;
}

/**
 * create
 */
function createMediaStyles(mediaQuery, styles): { // create function on StyleSheet.js
  ...
  compiledStyles = compileAndInsertAtomic(mediaQuery, styleObj);
  ...
}

/**
 * resolve
 */
function resolveMediaStyles(styles, options = {}) { // StyleSheet function on StyleSheet.js
  const isRTL = options.writingDirection === 'rtl';
  const styleProps: StyleProps = customStyleq(styles, options);
  if (Array.isArray(styleProps) && styleProps[1] != null) {
    styleProps[1] = inline(styleProps[1], isRTL);
  }
  return styleProps;
}

The resolve function should return the class names of the media styles, then we can use the default StyleSheet to apply the class names:

StyleSheet.create({
  $$css: true,
  _: resolveMediaStyles(mediaStyles)[0],
});