@micheldever/qwik-compose

1.1.0 • Public • Published


@micheldever/qwik-compose

A styled-components-like API for creating stylish Qwik primitives


git clone https://mtsweb@dev.azure.com/mtsweb/oss/_git/qwik-compose

Installation

This package is distributed via npm. You can install it as a dependency in your project by running:

yarn add @micheldever/qwik-compose

Usage

To create a composed component, access one of the element properties from the compose function and pass an array of class names to apply:

import { compose } from '@micheldever/qwik-compose';

// Create a <Wrapper> Qwik component that implements the following
// Tailwind CSS classes and renders as a <section> html element
const Wrapper = compose.section([
  'bg-orange-100',
  'p-16',
]);

// Create a <Title> Qwik component that implements the following
// Tailwind CSS classes and renders as a <h1> html element
const Title = compose.h1([
  'text-center',
  'text-2xl',
  'text-rose-400',
  'font-serif',
  'font-bold',
  'my-4',
]);

// Use them like regular Qwik components – except they're styled!
function Application() {
  return (
    <Wrapper>
      <Title>Hello World, this is my first composed component!</Title>
    </Wrapper>
  );
}

Transient Props

Since @micheldever/qwik-compose does not execute any runtime code, applying conditional styling can be tricky. As such, it is recommended that you lean on data attributes to use as hooks for your conditional styles.

To make this pattern quick and easy to manage, you can prefix your conditional prop names with a dollar sign ($) to turn them into transient props. This will then convert your prop into a data attribute pre-runtime. For example, a prop named $isLarge is not rendered to the final DOM element, but instead is transformed to be data-is-large which can then be used as a styling hook.

This functionality is entirely optional and you can still directly pass data attributes if that makes more sense to you.

const Button = compose.button([
  'bg-red-500',
  'text-red-50',
  'data-[is-large="true"]:text-xl',
]);

// outputs <button class="bg-red-500 text-red-50 data-[is-large="true"]:text-xl" data-is-large="true">
<Button $isLarge={true}>

Extending Components

As you start to build out your component library, you may wish to use a component but slightly change or build upon its styling. While you could use the transient props pattern above, depending on how many alterations you make, this could quickly become unmaintainable.

To simplify the process, you can extend an existing composed component and supply it with a list of additional classes you wish to attach, resulting in a new component that is the best of both worlds.

// outputs <button class="bg-red-500 text-red-50">
const Button = compose.button([
  'bg-red-500',
  'text-red-50',
]);

// outputs <button class="bg-red-500 text-red-50 border border-red-700">
const BorderedButton = compose(Button, [
  'border',
  'border-red-700'
]);

This also works for custom components, as long as they pass the class prop to a DOM element.

function CustomButton({ children, class }) {
  return <button class={class}>{children}</button>;
}

const ComposedCustomButton = composed(CustomButton, [
  'bg-red-500',
  'text-red-50',
]);

// outputs <button class="bg-red-500 text-red-50">
<ComposedCustomButton />

You can also pass extra classes to individual component instances via the class prop.

// outputs <button class="bg-red-500 text-red-50 border border-red-700">
<Button class='border border-red-700' />

Using as

All composed components are polymorphic, meaning you are able to alter the way they render after they have been created by using the as prop. This keeps all the styling that has been applied to a component but just switches out what is ultimately being rendered (be it a different HTML element or a different custom component).

const Button = compose.button([ ... ]);

// This component will render as a `div` element instead of a `button`
<Button as='div' />

Using attrs

Occasionally, you may know ahead of time if your component will always use the same static prop values, such as an input element having a set type property. By using the attrs method, you can implicitly set any static prop values that should be passed down to every instance of your component.

Furthermore, you can also use the attrs method to attach default values for your dynamic transient props.

const TextField = compose.input([ ... ]).attrs({ $hasIcon: false, type: 'text' });

// This will render with the `type` attribute implicitly set
// from the original declaration
<TextField />

// You can also locally override any attributes that are defined above
<TextField $hasIcon={true} type='email' />
// For extended components, you can define attributes in the same way
const EmailField = styled(TextField, [ ... ]).attrs({ type: 'email' });

Using toClass

If you just need the output class string and not the created Qwik component, you can call the toClass method on any composed component to receive the concatenated class.

// outputs <button class="bg-red-500 text-red-50">
const Button = compose.button([
  'bg-red-500',
  'text-red-50',
]);

// outputs bg-red-500 text-red-50
const className = Button.toClass();

Execution Hooks

@micheldever/qwik-compose allows you to perform additional custom logic to the concatenated class string by defining a global event handler for the onDone event. This can be particularly useful if you want to combine @micheldever/qwik-compose with a utility function such as tailwind-merge.

Example with tailwind-merge

// qwik-compose.config.ts

import { defineConfig } from '@micheldever/qwik-compose';
import { twMerge } from 'tailwind-merge';
import type { DefineConfigOptions } from '@micheldever/qwik-compose';


const config: DefineConfigOptions = {
  hooks: {
    onDone: (className) => twMerge(className),
  },
};

export const { compose } = defineConfig(config);
// components/button.ts

import { compose } from '../qwik-compose.config';

const Button = compose.button([
  'bg-orange-100',
  'bg-red-500',
]);

// outputs <button class="bg-red-500">
<Button />

Readme

Keywords

none

Package Sidebar

Install

npm i @micheldever/qwik-compose

Weekly Downloads

5

Version

1.1.0

License

MIT

Unpacked Size

14.9 kB

Total Files

5

Last publish

Collaborators

  • hwll