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

@jsxui/system

v0.1.12

Published

Utilities for building design systems with JavaScript.

Downloads

2

Readme

@jsxui/system

Utilities to configure your Design System for any framework or platform.

Install

yarn add @jsxui/system
npm install @jsxui/system

Get Started

This package ships with a helper function to create a design system configuration that returns context aware utilities.

createSystem

After installing, import the helper function and create your design system configuration:

import { createSystem } from "@jsxui/system"

export const { createProps, theme, tokens } = createSystem({
  mediaQueries: {
    small: { minWidth: 0 },
    medium: { minWidth: 960 },
    large: { minWidth: 1280 },
    dark: { prefersColorScheme: "dark" },
  },
  colors: {
    primary: { initial: "#0070f3", dark: "#0693d2" },
    secondary: { initial: "#ff4081", dark: "#c60055" },
    background: { initial: "#fafafa", dark: "#212121" },
    foreground: { initial: "#212121", dark: "#fafafa" },
  },
})

export type ColorValue = keyof typeof theme.colors

createProps

Use the createProps helper utility to create your first set of component props. We'll start by creating a set of style props to display different text style variations:

import { createProps, ColorValue, theme } from "system"

const textVariant = createProps({
  styles: {
    fontSize: (size: number) => size,
    color: (color: ColorValue) => ({ color: theme.colors[color] }),
  },
})

Notice that we start by defining [styles]. These are functions that will be applied to the variant's props and can return either a single value or an object of multiple values. If you've written Sass before, these are similar to mixins.

Now that we have a few styles defined, we can start to add each variant. We can also define any default props that should be applied to all variants like the text color:

import { createProps, ColorValue } from "system"

const textVariant = createProps({
  styles: {
    fontSize: (size: number) => size,
    color: (color: ColorValue) => ({ color: theme.colors[color] }),
  },
  defaults: {
    color: "foreground",
  },
  variants: {
    heading1: {
      as: "h1",
      fontSize: { initial: "2rem", medium: "3rem", large: "4rem" },
    },
    heading2: {
      as: "h2",
      fontSize: { initial: "1.5rem", medium: "2rem", large: "3rem" },
    },
    body: {
      as: "p",
      fontSize: { initial: "1rem", medium: "1.25rem", large: "1.5rem" },
    },
  },
})

And that's it! We've created our first variant that can be used to style text and display the appropriate element in our UI.

Using with other libraries

This text variant can be used with any library. For an example, let's see how we can use it with Styled Components, a popular CSS-in-JS library:

import type { AttributeProps, StyleProps } from "@jsxui/system"
import styled from "styled-components"
import { textVariant } from "system"

export type TextAttributeProps = AttributeProps<typeof textVariant>

export type TextStyleProps = StyleProps<typeof textVariant>

export const Text = styled.p.attrs<TextAttributeProps>(
  (props) => textVariant.getProps(props.variant, props.states).attributes
)<TextStyleProps>((props) => textVariant.getProps(props.variant, props.states).styles)

Now we can use our Text component in our application with fully typed variants and states:

import { Text } from "system"

function App() {
  return (
    <Text variant="heading1" color="primary">
      Hello World
    </Text>
  )
}

If you'd like to use CSS properties, use the collectVariants helper to create global styles:

import { useMemo } from "react"
import { ThemeKey } from "@jsxui/system"
import { collectVariants, theme } from "system"
import { createGlobalStyle, ThemeProvider } from "styled-components"

export function AppProvider() {
  const GlobalStyles = useMemo(() => {
    return createGlobalStyle(collectVariants())
  }, [])

  return (
    <>
      <GlobalStyles />
      <App />
    </>
  )
}

Now variants will reference a global CSS variable. This has the benefit of allowing us to easily change the theme without having to update all of our components and is particularly useful for server-side rendering.

Theming

Most applications need to be able to change the theme of their design system. This is where the theme object comes in.

Taking our previous example, we can create a theme object that can be used to change the colors of our text. We'll start by creating a Theme component:

import { ThemeKey } from "@jsxui/system"
import { collectVariants, theme } from "system"
import { createGlobalStyle, ThemeProvider } from "styled-components"

const GlobalStyles = createGlobalStyle(collectVariants)

const ThemeContext = React.createContext<ThemeKey<typeof theme>>(null)

export function AppProvider({ theme }: { theme: ThemeKey<typeof theme> }) {
  return (
    <>
      <GlobalStyles />
      <Theme value={theme}>
        <App />
      </Theme>
    </>
  )
}

Using the AppProvider component we can provide the proper CSS properties that can be used by leaf components.

import { AppProvider } from "system"

export function App({ children }) {
  return <AppProvider>{children}</AppProvider>
}

To set a specific theme for a tree of components, use the ThemeProvider component:

import { AppProvider, ThemeProvider } from "system"

export function App() {
  return (
    <AppProvider>
      <Text>Hello World</Text>
      {/* Force theme to always be dark regardless of user preference */}
      <Theme variant="dark">
        <Text>Hello World</Text>
      </Theme>
    </AppProvider>
  )
}

States

Variants can affect more than just the CSS properties. For example, a variant can affect the element type of a component. In our variant above we specified that the body variant should be rendered as an p element. What if we wanted to nest our Text component though? This would render inaccessible markup as we're nesting two block-level elements in one another.

To fix this, we can use the states object to specify which states a prop value can define. This allows us to swap out the element while in a specific state:

import { createContext } from "react"
import type { AttributeProps, StyleProps } from "@jsxui/system"
import styled from "styled-components"
import { textVariant } from "system"

export type TextAttributeProps = AttributeProps<typeof textVariant>

export type TextStyleProps = StyleProps<typeof textVariant>

export const TextDescendantContext = createContext(false)

export const Text = styled<TextAttributeProps>((props) => {
  const isDescendant = React.useContext(TextDescendantContext)
  const { as: Element, ...props } = textVariant.getProps(props.variant, {
    descendant: isDescendant,
    ...props.states,
  }).attributes

  return (
    <TextDescendantContext.Provider value={true}>
      <Element {...props} />
    </TextDescendantContext.Provider>
  )
})<TextStyleProps>((props) => textVariant.getProps(props.variant, props.states).styles)

Now this will allow us to render a span when the Text component is a descendant of itself giving us proper semantic markup.

Overriding

Each variant can be overriden.

import { textVariant } from "system"

textVariant.override("heading1", {
  fontSize: { initial: "2rem", medium: "3rem", large: "4rem" },
})

This is helpful inside of a component system if we want to be able to override the defaults for a tree:

Guides

Button

import type { StyleProps } from "@jsxui/system"
import type { BoxSizeValue, ColorValue } from "system"
import { createProps, mergeVariants, theme } from "system"
import styled from "styled-components"

const colorTransform = (color: ColorValue) => theme.colors[color]

const buttonVariant = createProps({
  name: "variant",
  states: ["disabled", "pressed", "focused"],
  styles: {
    backgroundColor: colorTransform,
    color: colorTransform,
    borderColor: colorTransform,
    borderSize: (size: number) => size,
    opacity: (opacity: number) => opacity,
  },
  defaults: {
    opacity: { disabled: 0.65 },
    variant: "primary",
  },
  variants: {
    primary: {
      color: "foreground",
      backgroundColor: "interactiveBackgroundPrimary",
    },
    primaryOutline: {
      color: "interactiveForegroundPrimary",
      borderColor: "interactiveBorderPrimary",
      borderSize: 1,
    },
    secondary: {
      color: "foreground",
      backgroundColor: "interactiveBackgroundSecondary",
    },
    secondaryOutline: {
      color: "interactiveForegroundSecondary",
      borderColor: "interactiveBorderSecondary",
      borderSize: 1,
    },
  },
})

const sizeVariant = createProps({
  name: "size",
  styles: {
    fontSize: (size: string) => size,
    minHeight: (size: number) => theme.boxSizes[size],
    spaceAround: (size: number) => {
      const value = theme.boxSpacings[size]
      return {
        paddingLeft: value,
        paddingRight: value,
      }
    },
  },
  variants: {
    small: {
      fontSize: "16px",
      minHeight: "20px",
      spaceAround: "4px",
    },
    medium: {
      fontSize: "20px",
      minHeight: "24px",
      spaceAround: "8px",
    },
    large: {
      fontSize: "24px",
      minHeight: "32px",
      spaceAround: "16px",
    },
  },
})

const variants = mergeVariants(buttonVariant, sizeVariant)

export const Button = styled.button<StyleProps<typeof variants>>((props) =>
  variants.getProps(props.variant, { disabled: props.disabled })
)

// Example
function App() {
  return (
    <>
      <Button size={{ initial: "small", medium: "large" }} variant="secondary">
        Hello World
      </Button>
      <Button disabled>Disabled</Button>
    </>
  )
}

Image

import { createProps } from "system"

const imageVariant = createProps({
  styles: {
    width: (value: string) => value,
  },
})

const Image = styled(({ source, title }) => {
  return source.dark ? (
    <picture>
      <source media="(prefers-color-scheme:dark)" srcset={source.dark} />
      <img src={source.default} title={title} />
    </picture>
  ) : (
    <img src={source.default} title={title} />
  )
})({ display: "block" }, (props) => imageVariant.getProps(props.variant).styles)

// Example
function App() {
  return (
    <Image
      source={{
        initial: "light.jpg",
        dark: "dark.jpg",
      }}
    />
  )
}

Future Ideas

Composable Systems

Composable systems allow a whole system to be easily and deterministically overridden.

import { createSystem } from "@jsxui/system"

const system = createSystem({
  mediaQueries: {
    small: { minWidth: 640 },
    medium: { minWidth: 1024 },
    large: { minWidth: 1440 },
  },
  colors: {
    background: "#fff",
    foreground: "#000",
  },
})

const alternateSystem = createSystem(system, {
  colors: {
    background: "#000",
    foreground: "#fff",
  },
})

const textProps = alternateSystem.createProps({
  transforms: {
    color: (size: keyof typeof alternateSystem.theme.colors) => {
      return alternateSystem.theme.colors[size]
    },
  },
  defaults: {
    variant: "body",
  },
  variants: {
    body: {
      color: "foreground",
    },
  },
})

const Text = styled.span(textVariant.getProps)

function App() {
  return <Text>Hello World</Text> // color: #fff
}

Composable Props

Composable props allow prop definitions to be easily and deterministically overridden.

import { createProps } from "system"

const textProps = createProps({
  variants: {
    heading1: {
      fontSize: { initial: "2rem", medium: "3rem", large: "4rem" },
    },
  },
})

// Only override the font size at the largest breakpoint
const alternateTextProps = createProps(textProps, {
  variants: {
    heading1: {
      fontSize: { large: "6rem" },
    },
  },
})