npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mdx-breadboard

v1.1.0

Published

Edit an MDX file in real time, with preview.

Downloads

2

Readme

mdx-breadboard

npm version

A themeable React component. Use it to edit an MDX file's source in real time.

Only use this component to display your own code -- it is not safe for use with publicly submitted code. For public code, use a service like codepen.io.

Installation

yarn add mdx-breadboard

Usage

There are currently three Breadboard components:

  • RawBreadboard

    Expects that the code will call ReactDOM.render() itself, with the result placed in document.getElementById('app'). Provides global React and ReactDOM objects.

  • ComponentBreadboard

    Expects that a script will import React, and export a default Component. Imports and renders the component.

  • MDXBreadboard

    Expects a Markdown document and compiles it to a React Component with mdxc.

Props

Here is an overview of the props available for all Breadboard components. For full details on each component's available props, see the propTypes definition in the source.

  • defaultSource required

    The source to execute. Breadboards are uncontrolled; they currently do not emit events when the code updates/renders. PRs to add this functionality would be welcome.

  • theme required

    The theme object that actually renders the Breadboard. This is required as the Breadboard components themselves do not generate any HTML. For an example of a Breadboard theme, see the theme section.

  • require

    The require function that will be used when executing the Breadboard's source. Use this to configure how import statements work.

    By default, Breadboard provides a require function that just makes react available.

  • defaultMode

    Specifies the mode that the Breadboard will be in when loaded. Available options are:

    • source
    • transformed
    • view
    • console
  • defaultSecondary

    If your Breadboard element has enough space, it will split the view into two panels. In this case, the source panel will always be displayed. This option chooses a default for the second panel. All options from above are available -- except source.

Themes

Different websites call for default themes. Of course, CSS isn't always sufficient to make the theming changes that you'd like. Because of this, Breadboards do not generate any Markup themselves. Instead, they leave the markup generation to you, via theme objects.

For an example of how theming can be used in practice, see MDXC Playground. This page uses two Breadboard themes:

  • The "fullscreen" theme renders the document's source on the left, and the full document on the right. (source)
  • The "default" theme is used for embedded examples within the right pane. (source)

The actual options available on a theme object differ between breadboards. For details, you'll currently need to view the source.

Example

This is an example of a theme for RawBreadboard and ComponentBreadboard that renders the editor using CodeMirror. This is used on reactarmory.com

import './defaultBreadboardTheme.less'
import React, { Component, PropTypes } from 'react'
import debounce from 'lodash.debounce'
import codeMirror from 'codemirror'
import createClassNamePrefixer from '../utils/createClassNamePrefixer'

require("codemirror/mode/jsx/jsx")


const cx = createClassNamePrefixer('defaultBreadboardTheme')


export default {
  maxSinglePaneWidth: 800,
  
  renderBreadboard: function(props) {
    const {
      consoleMessages,
      transformedSource,
      transformError,
      executionError,

      renderEditorElement,
      renderMountElement,

      modes,
      modeActions,

      reactVersion,
      appId
    } = props

    const activeModeCount = Object.values(modes).reduce((acc, x) => acc + x || 0, 0)

    const sourceLayout = {
      position: 'relative',
      flexBasis: 600,
      flexGrow: 0,
      flexShrink: 0,
    }
    if (activeModeCount === 1) {
      sourceLayout.flexShrink = 1
    }

    const secondaryLayout = {
      position: 'relative',
      flexBasis: 600,
      flexGrow: 0,
      flexShrink: 1,
      overflow: 'auto',
    }
      
    return (
      <div className={cx.root(null, activeModeCount == 1 ? 'single' : null)}>
        { (consoleMessages.length || activeModeCount == 1) &&
          <nav>
            <span className={cx('modes')}>
              { activeModeCount === 1 &&
                <span className={cx('mode', { active: modes.source })} onClick={modeActions.selectSource}>Source</span>
              }
              <span className={cx('mode', { active: modes.view })} onClick={modeActions.selectView}>Preview</span>
              <span className={cx('mode', { active: modes.console })} onClick={modeActions.selectConsole}>Console</span>
            </span>
          </nav>
        }
        { modes.source &&
          renderEditorElement({ layout: sourceLayout })
        }
        { // Always render the preview element, as the user's code may depend
          // on it being available. Hide it if it isn't selected.
          <div className={cx('preview', { 'preview-visible': modes.view && !transformError && !executionError })} style={secondaryLayout}>
            {renderMountElement()}
          </div>
        }
        { modes.console && !transformError && !executionError &&
          <BreadboardConsole
            className={cx('console')}
            style={secondaryLayout}
            messages={consoleMessages}
          />
        }
        { (transformError || executionError) && 
          <div className={cx('error')} style={secondaryLayout}>
            <pre>
              <span className={cx('error-title')}>Failed to Compile</span>
              {(transformError || executionError).toString()}
            </pre>
          </div>
        }
      </div>
    )
  },

  renderEditor: function({ layout, value, onChange }) {
    return (
      <JSXEditor
        className={cx('editor')}
        value={value}
        onChange={onChange}
        style={layout}
      />
    )
  },
}

const getType = function (el) {
  let t = typeof el;

  if (Array.isArray(el)) {
    t = "array";
  } else if (el === null) {
    t = "null";
  }

  return t;
};

// Based on react-playground by Formidable Labs
// See: https://github.com/FormidableLabs/component-playground/blob/master/src/components/es6-preview.jsx
const wrapMap = {
  wrapnumber(num) {
    return <span style={{color: "#6170d5"}}>{num}</span>;
  },

  wrapstring(str) {
    return <span style={{color: "#F2777A"}}>{"'" + str + "'"}</span>;
  },

  wrapboolean(bool) {
    return <span style={{color: "#48A1CF"}}>{bool ? "true" : "false"}</span>;
  },

  wraparray(arr) {
    return (
      <span>
        {"["}
        {arr.map((entry, i) => {
          return (
            <span key={i}>
              {wrapMap["wrap" + getType(entry)](entry)}
              {i !== arr.length - 1 ? ", " : ""}
            </span>
          );
        })}
        {"]"}
      </span>
    );
  },

  wrapobject(obj) {
    const pairs = [];
    let first = true;

    for (const key in obj) {
      pairs.push(
        <span key={key}>
          <span style={{color: "#8A6BA1"}}>
            {(first ? "" : ", ") + key}
          </span>
          {": "}
          {wrapMap["wrap" + getType(obj[key])](obj[key])}
        </span>
      );

      first = false;
    }

    return <i>{"Object {"}{pairs}{"}"}</i>;
  },

  wrapfunction() {
    return <i style={{color: "#48A1CF"}}>{"function"}</i>;
  },

  wrapnull() {
    return <span style={{color: "#777"}}>{"null"}</span>;
  },

  wrapundefined() {
    return <span style={{color: "#777"}}>{"undefined"}</span>;
  }
}


function BreadboardConsole({ className, messages, style }) {
  return (
    <div className={cx('console')} style={style}>
      {messages.map(({ type, args }, i) =>
        <div key={'message'+i} className={cx('messages')}>
          {args.map((arg, i) =>
            <div key={'arg'+i} className={cx('arg')}>{wrapMap["wrap" + getType(arg)](arg)}</div>
          )}
        </div>
      )}
    </div>
  )
}


function normalizeLineEndings (str) {
  if (!str) return str;
  return str.replace(/\r\n|\r/g, '\n');
}

// Based on these two files:
// https://github.com/JedWatson/react-codemirror/blob/master/src/Codemirror.js
// https://github.com/FormidableLabs/component-playground/blob/master/src/components/editor.jsx
class JSXEditor extends Component {
  static propTypes = {
    theme: PropTypes.string,
    readOnly: PropTypes.bool,
    value: PropTypes.string,
    selectedLines: PropTypes.array,
    onChange: PropTypes.func,
    style: PropTypes.object,
    className: PropTypes.string
  }

  static defaultProps = {
    theme: "monokai",
  }

  state = {
    isFocused: false,
  }

  constructor(props) {
    super(props)

    this.componentWillReceiveProps = debounce(this.componentWillReceiveProps, 0)
  }

  componentDidMount() {
    const textareaNode = ReactDOM.findDOMNode(this.refs.textarea);
    const options = {
      mode: "jsx",
      lineNumbers: false,
      lineWrapping: false,
      smartIndent: false,
      matchBrackets: true,
      theme: this.props.theme,
      readOnly: this.props.readOnly,
      viewportMargin: Infinity,
    }

    this.codeMirror = codeMirror.fromTextArea(textareaNode, options);
    this.codeMirror.on('change', this.handleChange);
    this.codeMirror.on('focus', this.handleFocus.bind(this, true));
    this.codeMirror.on('blur', this.handleFocus.bind(this, false));
    this.codeMirror.on('scroll', this.handleScroll);
    this.codeMirror.setValue(this.props.defaultValue || this.props.value || '');
  }

  componentWillReceiveProps(nextProps) {
    if (this.codeMirror && nextProps.value !== undefined && normalizeLineEndings(this.codeMirror.getValue()) !== normalizeLineEndings(nextProps.value)) {
      if (this.props.preserveScrollPosition) {
        var prevScrollPosition = this.codeMirror.getScrollInfo();
        this.codeMirror.setValue(nextProps.value);
        this.codeMirror.scrollTo(prevScrollPosition.left, prevScrollPosition.top);
      } else {
        this.codeMirror.setValue(nextProps.value);
      }
    }
  }

  componentWillUnmount() {
    // is there a lighter-weight way to remove the cm instance?
    if (this.codeMirror) {
      this.codeMirror.toTextArea();
    }
  }

  highlightSelectedLines = () => {
    if (Array.isArray(this.props.selectedLines)) {
      this.props.selectedLines.forEach(lineNumber =>
        this.codeMirror.addLineClass(lineNumber, "wrap", "CodeMirror-activeline-background"))
    }
  }

  focus() {
    if (this.codeMirror) {
      this.codeMirror.focus()
    }
  }

  render() {
    return (
      <div className={cx(this.props.className, 'jsx-editor', { focused: this.state.isFocused })} style={this.props.style}>
        <textarea
          ref="textarea"
          defaultValue={this.props.value}
          autoComplete="off"
        />
      </div>
    )
  }

  handleFocus(isFocused) {
    this.setState({ isFocused })
  }

  handleChange = (doc, change) => {
    if (!this.props.readOnly && this.props.onChange && change.origin !== 'setValue') {
      this.props.onChange(doc.getValue())
    }
  }

  handleScroll = (codeMirror) => {
    this.props.onScroll && this.props.onScroll(codeMirror.getScrollInfo())
  }
}