Skip to content

Latest commit

 

History

History
1094 lines (811 loc) · 24 KB

README.md

File metadata and controls

1094 lines (811 loc) · 24 KB

Modifiers with autocomplete for Styled Components and Linaria

This library allows you to write modifiers for Styled Components and Linaria via Dot Notation and with autocomplete. As well as automatically generate for them types. And that's not all 😎.

Features

  • Quickly writing styles in relation to modifiers
  • Using modifiers via Dot Notation
  • Writing modifiers with autocomplete
  • Uniform modifiers and their values
  • Automatic type generation for modifiers
  • Possibility to use custom modifiers
  • Applying styles relative to multiple modifiers or multiple values
  • Ultra-small size (0.4 KB gzip)

A Quick Look

Demo

Live demo

Usage

Apply styles for any size property value or any color value:

import { mods, Mods } from '../shared/styled';

export const StyledComponent = styled.div<Mods<'size' | 'color'>>`
    // Call as literals
    ${mods.size`
        padding: 20px;
        margin: 32px;
    `};

    // Call as function
    ${mods.color(
        (value) => css`
            color: ${value};
            border: 1px solid;
        `
    )};
`;
<StyledComponent color="blue" size="small" />

Apply styles for size='small' or disabled={true}:

import { mods, Mods } from '../shared/styled';

export const StyledComponent = styled.div<
    Mods<'size', 'small' | 'medium'> & Mods<'disabled'>
>`
    ${mods.size.small`
        font-size: 14px;
    `};

    ${mods.size.medium`
        font-size: 16px;
    `};

    ${mods.disabled.true`
        color: gray;
    `};
    
    ${mods.disabled.false`
        color: black;
    `};
`;
<StyledComponent size="small" disabled />

Apply styles if size is undefined or color is not blue:

import { mods, Mods } from '../shared/styled';

export const StyledComponent = styled.div<Mods<'size' | 'color'>>`
    ${mods.not.size`
        font-size: 14px;
    `};

    ${mods.not.color.blue`
        background: transparent;
    `};
`;
<StyledComponent color="green" />

Apply styles for any value of the customProp property:

import { mods, Mods } from '../shared/styled';

export const StyledComponent = styled.div<{ customProp: string }>`
    ${mods('customProp')`
        background: black;
    `};
`;
<StyledComponent customProp="something" />

Apply styles for customProp='customValue':

import { mods, Mods } from '../shared/styled';

export const StyledComponent = styled.div<{ customProp: string }>`
    ${mods('customProp', 'customValue')`
        background: black;
    `};
`;
<StyledComponent customProp="customValue" />

☝️ These are not all possible uses of modifiers. View more options

Setup

src/shared/styled/config.ts

// Configure modifier names and their values
const config = {
    color: ['red', 'green', 'blue'],
    size: ['small', 'medium', 'large'],
    spacing: [8, 12],
    disabled: [true, false],
} as const;

src/shared/styled/types.ts

import { ModifiersConfig, Modifiers } from '@styled-kit/mods';

// Creating types for modifiers
type ModsConfig = ModifiersConfig<typeof config>;

export type Mods<
    Names extends keyof ModsConfig = keyof ModsConfig,
    Value extends ModsConfig[Name] = undefined
> = Modifiers<ModsConfig, Name, Value>;

src/shared/styled/index.ts

import { initMods } from '@styled-kit/mods';
import { config } from './config';

// Initializing modifiers by config
export const mods = initMods(config);
export type { Mods } from './types';

Documentation

Installation

You can install the library using the npm or Yarn package manager.

Installation example using npm:

npm install @styled-kit/mods

An example installation using Yarn:

yarn add @styled-kit/mods

Setup

To be able to use modifiers via Dot Notation, you must define in advance what they can be and what their values are.

Creating a modifiers configuration

To configure modifiers, create a config configuration object.

You can create this object in any file in your project, but as an option, you can make such a file system:

src/shared/styled/config.ts

const config = {
    color: ['red', 'green', 'blue'],
    size: ['small', 'medium', 'large'],
    spacing: [8, 12],
    disabled: [true, false],
} as const;

Describe in config what modifiers with what values you can have. Describe those modifiers that you are going to reuse in different components. Over time, you can add to this object as new modifiers are needed.

Note: In TypeScript you must specify the as const type for the config object, to fix its values and ensure that typing works correctly.

Creating a types

To use autocomplete and types for modifiers, you must configure the appropriate TypeScript types.

You can make type settings in any file of your project, but as an option, you can make such a file system:

src/shared/styled/types.ts

import { ModifiersConfig, Modifiers } from '@styled-kit/mods';

type ModsConfig = ModifiersConfig<typeof config>;

export type Mods<
    Names extends keyof ModsConfig = keyof ModsConfig,
    Value extends ModsConfig[Name] = undefined
> = Modifiers<ModsConfig, Name, Value>;

export type RMods<
    Names extends keyof ModsConfig = keyof ModsConfig,
    Value extends ModsConfig[Name] = undefined
> = Required<Mods<Name, Value>>;

ModsConfig is a type representing the configuration structure of modifiers. It is only needed to create a Mods type.

Mods is a type representing modifiers, where N is the name of the modifier, and V is the value of the modifier (optional). All modifiers can be undefined.

RMods This is a copy of Mods, but all modifiers Required and cannot be undefined.

Initializing the modifiers

You can initialize the modifiers in any file of your project, but it is important that the initialization is done only once at application startup. The mods object can then be reused in components of your application.

src/shared/styled/index.ts

import { initMods } from '@styled-kit/mods';
import { config } from './config';

export const mods = initMods(config);
export type { Mods, RMods } from './types';

Initializing options

When initializing modifiers, you can set some options.

export const mods = initMods(config, {
    onlyFalseValues: true,
});

onlyFalseValues

(default: false)

If true, then modifiers with value false will not work when undefined is set.

onlyFalseValues: true

import { mods, Mods } from '../shared/styled';

export const StyledComponent = styled.div<Mods<'disabled'>>`
    ${mods.disabled.false`
        color: blue;
    `};
`;
// Styles will not be applied because disabled = undefined
<StyledComponent /> 

onlyFalseValues: false

import { mods, Mods } from '../shared/styled';

export const StyledComponent = styled.div<Mods<'disabled'>>`
    ${mods.disabled.false`
        color: blue;
    `};
`;
// Styles will be applied
<StyledComponent /> 

Usage types

Type Mods

This type allows you to define which properties a particular Styled Component can take.

Mods<'size'> - returns the type of object with the size modifier, with all its values from config:

type ComponentProps = Mods<'size'>;

// ComponentProps = {
//    size?: 'small' | 'medium' | 'large';
// }

Mods<'size' | 'disabled'> - returns object type with size and disabled modifiers, with all their values from config:

type ComponentProps = Mods<'size' | 'disabled'>;

// ComponentProps = {
//    size?: 'small' | 'medium' | 'large';
//    disabled?: boolean;
// }

Mods<'size', 'small' | 'large'> – Returns the object type with the size modifier, with small and large values:

type ComponentProps = Mods<'size', 'small' | 'large'>;

// ComponentProps = {
//    size?: 'small' | 'large';
// }

Mods<'size', 'small'> & Mods<'disabled', true> & Mods<'spacing', 12> – returns the object type with size, disabled and spacing modifiers, with the selected values:

type ComponentProps = Mods<'size', 'small'> &
    Mods<'disabled', true> &
    Mods<'spacing', 12>;

// ComponentProps = {
//    size?: 'small';
//    disabled?: true;
//    spacing?: 12;
// }

Type RMods

As stated earlier, this is a copy of the Mods type, but wrapped in Required. This removes the possibility of passing a undefined value to the modifier.

You may need RMods if you want to make the modifier required:

interface ComponentProps extends RMods<'size'> {}

// interface ComponentProps {
//    size: 'small' | 'medium' | 'large';
// }

Type StyledMods

Since version 16 of React all specified attributes remain on DOM elements and are not removed even if React does not recognize them. That said, if these attributes are not known to React, you will see a Warning in the developer console in the browser.

To avoid these warnings, Styled Components recommends use prefix $ in front of properties that should not be included in the DOM element.

The StyledMods type will allow you to write Styled Component property types without the $ prefix, but still have valid typing. Internally, it prefixes all types passed to it with $ to all the types passed to it.

Without the StyledMods type:

export const StyledComponent = styled.div<Mods<'size'> & { padding: string; }>`
    ${mods.size.small`
        font-size: 14px;
    `};

    padding: ${({ padding }) => padding};
`;
<StyledComponent size="small" padding="12px" />

With the StyledMods type:

import { StyledMods } from '@styled-kit/mods';

export const StyledComponent = styled.div<
    StyledMods<Mods<'size'> & { padding: string }>
>`
    ${mods.size.small`
        font-size: 14px;
    `};

    padding: ${({ $padding }) => $padding};
`;
<StyledComponent $size="small" $padding="12px" />

☝️ You do not need to add the $ prefix to the mods modifiers. Modifiers are automatically searched with and without the prefix.

Usage modifiers

The library assumes a large number of options for using modifiers. Each of them can be called as a literal, as a function, with negation, without negation, in object mode or in function mode.

Let's take a closer look at how modifiers can be used:

Using in Object mode

The Object mode allows you to use Dot Notation to use modifiers that you enter in config beforehand.

This mode is best suited for applying styles relative to common modifiers that will be reused in other components.

The features of Object mode modifiers:

  • Writing modifiers with Dot Notation
  • When writing modifiers, you will have autocomplete
  • Types will be automatically generated based on your config, which you can use with the Mods type that you create when you initialize the library
  • You will get modifiers that are reused throughout the project in a single format

Styles will be applied if:

color is not undefined

export const ModsColor = styled.div<Mods<'color'>>`
    ${mods.color`
        color: blue;
    `};
`;
<ModsColor color="blue" />

color is undefined

export const ModsNotColor = styled.div<Mods<'color'>>`
    ${mods.not.color`
        color: blue;
    `};
`;
<ModsNotColor />

color is blue and is not undefined

export const ModsColorBlue = styled.div<Mods<'color'>>`
    ${mods.color.blue`
        color: blue;
    `};
`;
<ModsColorBlue color="blue" />

color is not blue and is not undefined

export const ModsNotColorBlue = styled.div<Mods<'color'>>`
    ${mods.not.color.blue`
        color: white;
    `};
`;
<ModsNotColorBlue color="white" />

Using in Function mode

The Function mode allows you to apply styles using modifiers that are not in your config.

This mode is best suited for applying styles relative to private modifiers which will not be reused in other components.

The features of Function mode modifiers:

  • Using custom modifiers that are not in config
  • Ability to specify styles relative to multiple modifiers
  • Ability to specify styles relative to multiple modifier values
  • Types for them are written manually
  • No autocomplete when writing

Styles will be applied if:

color is not undefined

export const FnModsColor = styled.div<{ color?: string }>`
    ${mods('color')`
        color: blue;
    `};
`;
<FnModsColor color="blue" />

color is undefined

export const FnModsNotColor = styled.div<{ color?: string }>`
    ${mods.not('color')`
        color: blue;
    `};
`;
<FnModsNotColor />

color is blue and is not undefined

export const FnModsColorBlue = styled.div<{ color?: string }>`
    ${mods('color', 'blue')`
        color: blue;
    `};
`;
<FnModsColorBlue color="blue" />

color is not blue and is not undefined

export const FnModsNotColorBlue = styled.div<{ color?: string }>`
    ${mods.not('color', 'blue')`
        color: black;
    `};
`;
<FnModsNotColorBlue color="black" />

color is blue or black and is not undefined

export const FnModsColorBlueBlack = styled.div<{ color?: string }>`
    ${mods('color', ['blue', 'black'])`
        color: black;
    `};
`;
<FnModsColorBlueBlack color="black" />

color is not blue or black and is not undefined

export const FnModsNotColorBlueBlack = styled.div<{ color?: string }>`
    ${mods.not('color', ['blue', 'black'])`
        color: white;
    `};
`;
<FnModsNotColorBlueBlack color="white" />

color and bg is not undefined

export const FnModsColorBg = styled.div<{ color?: string; bg?: string }>`
    ${mods(['color', 'bg'])`
        color: black;
    `};
`;
<FnModsColorBg color="black" bg="green" />

color and bg is undefined

export const FnModsNotColorBg = styled.div<{ color?: string; bg?: string }>`
    ${mods.not(['color', 'bg'])`
        color: black;
    `};
`;
<FnModsNotColorBg />

color and bg is blue and is not undefined

export const FnModsColorBgBlue = styled.div<{ color?: string; bg?: string }>`
    ${mods(['color', 'bg'], 'blue')`
        color: black;
    `};
`;
<FnModsColorBgBlue color="blue" bg="blue" />

color and bg is not blue and is not undefined

export const FnModsNotColorBgBlue = styled.div<{ color?: string; bg?: string }>`
    ${mods.not(['color', 'bg'], 'blue')`
        color: black;
    `};
`;
<FnModsNotColorBgBlue color="black" bg="green" />

color and bg together is blue or green and is not undefined

export const FnModsColorBgBlueGreen = styled.div<{
    color?: string;
    bg?: string;
}>`
    ${mods(['color', 'bg'], ['blue', 'green'])`
        color: black;
    `};
`;
<FnModsColorBgBlueGreen color="blue" bg="blue" />

color and bg together is not blue or green and is not undefined

export const FnModsNotColorBgBlueGreen = styled.div<{
    color?: string;
    bg?: string;
}>`
    ${mods.not(['color', 'bg'], ['blue', 'green'])`
        color: black;
    `};
`;
<FnModsNotColorBgBlueGreen color="blue" bg="green" />

Call as a literals

You can call any modifier, regardless of mode, as a literal and immediately pass the necessary styles.

Object mode

export const StyledComponent = styled.div<Mods<'size'>>`
    ${mods.size`
        padding: 20px;
    `};

    ${mods.size.small`
        font-size: 14px;
    `};
`;
<StyledComponent size="small" />

Function mode

export const StyledComponent = styled.div<Mods<'size'>`
    ${mods('size')`
        font-size: 16px;
    `};

    ${mods('size', 'small')`
        font-size: 14px;
    `};
`;
<StyledComponent size="small" />

Call as a function

You can call any modifier, regardless of mode, as a function that will return the css method from styled-components.

The feature of this method is that you get possible values and all the properties of the styled component in the arguments of the passed callback function.

Object mode

export const StyledComponent = styled.div<Mods<'size'> & { padding: string }>`
    ${mods.size(
        (value, props) => css`
            font-size: ${value === 'small' ? '14px' : '16px'};
            padding: ${props.padding};
        `
    )};

    ${mods.size.small(
        (value, props) => css`
            font-size: 14px;
            padding: ${props.padding};
        `
    )};
`;
<StyledComponent size="small" />

Function mode

export const StyledComponent = styled.div<Mods<'size'> & { padding: string }>`
    ${mods('size')(
        (value, props) => css`
            font-size: ${value === 'small' ? '14px' : '16px'};
            padding: ${props.padding};
        `
    )};

    ${mods(
        'size',
        'small'
    )(
        (value, props) => css`
            font-size: 14px;
            padding: ${props.padding};
        `
    )};
`;
<StyledComponent size="small" />

☝️ IMPORTANT: If you use Function mode, use modifiers from config with type Mods and pass an array of modifiers or an array of modifier values, be sure to put as const after the array so that TypeScript correctly displays which properties will be available in the value argument passed to the callback function.

For example:

const config = {
    color: 'white' | 'blue' | 'black',
    bg: 'green' | 'blue' | 'black',
    ...
};

export const StyledComponent = styled.div<Mods<'color' | 'bg'>>`
    ${mods('color', ['blue', 'black'] as const)(
        (value, props) => css`
            // value: 'blue' | 'black'
        `
    )};

    ${mods.not('color', ['blue', 'black'] as const)(
        (value, props) => css`
            // value: 'white'
        `
    )};

    ${mods(['color', 'bg'] as const)(
        (value, props) => css`
            // value: { color: 'white' | 'blue' | 'black', bg: 'green' | 'blue' | 'black' }
        `
    )};

    ${mods.not(['color', 'bg'] as const)(
        (value, props) => css`
            // value: { color: undefined, bg: undefined }
        `
    )};

    ${mods(['color', 'bg'] as const, 'blue')(
        (value, props) => css`
            // value: 'blue'
        `
    )};

    ${mods.not(['color', 'bg'] as const, 'blue')(
        (value, props) => css`
            // value: { color: 'white' | 'black', bg: 'green' | 'black' }
        `
    )};

    ${mods(['color', 'bg'] as const, ['blue', 'black'] as const)(
        (value, props) => css`
            // value: { color: 'blue' | 'black', bg: 'blue' | 'black' }
        `
    )};

    ${mods.not(['color', 'bg'] as const, ['blue', 'black'] as const)(
        (value, props) => css`
            // value: { color: 'white' | 'blue' | 'black', bg: 'green' | 'blue' | 'black' }
        `
    )};
`;

Using mixins

To apply a set of styles for different modifier values, you can create mixins.

const sizeMixin = (
    spacing: RMods['spacing'],
    fontSize: RMods['fontSize']
) => css`
    padding: ${spacing}px;
    font-size: ${fontSize}px;
`;

const StyledComponen = styled.div<Mods<'size'>>`
    ${mods.size.small(sizeMixin(12, 16))};
    ${mods.size.medium(sizeMixin(16, 20))};
    ${mods.size.large(sizeMixin(20, 24))};
`;

Additional features

You can use modifiers with boolean values and pass them as string and as boolean:

export const StyledComponent = styled.div<Mods<'disabled'>>`
    ${mods.disabled.true`
        color: gray;
    `};

    ${mods('disabled', true)`
        color: gray;
    `};

    ${mods('disabled', 'true')`
        color: gray;
    `};

    ${mods.disabled.false`
        color: black;
    `};

    ${mods('disabled', false)`
        color: black;
    `};

    ${mods('disabled', 'false')`
        color: black;
    `};
`;
<StyledComponent disabled />

You can use modifiers with number values:

export const StyledComponent = styled.div<Mods<'spacing'>>`
    ${mods.spacing[12]`
        padding: 12px;
    `};

    ${mods.spacing[24]`
        padding: 24px;
    `};
`;
<StyledComponent spacing={12} />

You can do any nesting of modifiers:

export const StyledComponent = styled.div<Mods<'spacing' | 'size'>>`
    ${mods.size.small`
        font-size: 14px;
    `};

    ${mods.spacing[12]`
        padding: 12px;
        
        ${mods.size.small`
            font-size: 16px;
        `};
    `};
`;
<StyledComponent spacing={12} size="small" />

You can write additional logic for processing modifier values:

export const StyledComponent = styled.div<{ src: string }>`
    ${mods('src')(
        (value) => css`
            color: ${value.includes('facebook') ? 'blue' : 'orange'};
        `
    )};
`;
<StyledComponent src='https://www.facebook.com/' />
<StyledComponent src='https://www.instagram.com/' />

Highlight syntax

In order to make syntax highlighting in the IDE, you need to install the styled-components plugin.

In most of the plugins for styled-components it is possible to add a keyword in relation to which the syntax highlighting will be performed.

For example, plugin for IDE WebStorm.

After installing the plugin, go to WebStorm/Preferences/Languages & Frameworks/JavaScript/Styled Components, click on + and add the keyword mods.

After that, the CSS syntax will be highlighted in the styles defined in mods.

Editor Plugins

VS Code

WebStorm

Atom

Sublime Text