Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

colorfully

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

colorfully

Use Colorfully to define your interface theme!

  • 0.2.1
  • latest
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
2
Maintainers
1
Weekly downloads
 
Created
Source

Colorfully

使用 Colorfully 来定义你的界面主题吧!

✨特性

Colorfully 的核心功能就是使用 JavaScript 控制 CSS Variable 以及 HTML Attrbute,来达到动态主题的效果。

  • 强大:局部主题
  • 灵活:编程式创建动态导入导出配置
  • 激发:自定义主题包
  • 友好:TypeScript 类型支持CSS Variable 智能提示
  • 迅速:快速接入主流组件库脚手架快速创建主题包
  • 包容:小程序支持自定义渲染

📦安装

npm i colorfully

使用

Colorfully 的使用方式有很多种,这也使得它足以支撑大多数应用场景的原因。

初始化

初始化创建主题,具有更好的类型支持。

你可以使用它:自定义主题包

创建样式

首先我们创建一个色彩样式,同时定义其下样式类型以及样式变量。

import { CSSStyle } from 'colorfully';

export const color = new CSSStyle('色彩', 'color', {
    light: {
        name: '浅色',
        code: 'light',
        variables: {
            '--color-1': {
                name: '',
                code: '--color-1',
                value: '#e6fffb'
            },
            '--color-2': {
                name: '',
                code: '--color-2',
                value: '#b5f5ec'
            },
            ...{ /* more */}
        }
    },
    dark: {
        name: '深色',
        code: 'dark',
        variables: {
            '--color-1': {
                name: '',
                code: '--color-1',
                value: '#112123'
            },
            '--color-2': {
                name: '',
                code: '--color-2',
                value: '#113536'
            },
            ...{ /* more */}
        }
    }
})

你还可以创建更多,如:shadowspace、...

统一导出样式

import { CSSStyle } from 'colorfully';
import { color } from './color';
import { shadow } from './shadow';
import { space } from './space';

export const defaultStyleMap = { color, space, shadow };

如果你想在创建方案时有更友好的提示,你可以这样做:

export type DefaultStyleMap<T = typeof defaultStyleMap> = {
  [K in keyof T]: T[K] extends CSSStyle<infer V> ? V : void;
};
创建方案

接下来我们创建一个主题方案:

import { DefaultStyleMap } from '../style';
import { CSSSchema } from 'colorfully';

export const lightSchema = new CSSSchema<DefaultStyleMap>('浅色主题', 'light', {
  color: 'light',
  shadow: 'default',
  space: 'default'
});

你还可以创建更多,如:darkSchemadefaultSchema、...

统一导出方案

import { defaultSchema } from './default';
import { darkSchema } from './dark';
import { lightSchema } from './light';

export const defaultSchemaMap = { default: defaultSchema, dark: darkSchema, light: lightSchema };
实例化

创建好了主题和方案之后,我们就可以去实例化 Colorfully 类了。

import { Colorfully } from 'colorfully';
import { defaultStyleMap } from './style';
import { defaultSchemaMap } from './schema';

const theme = new Colorfully({ styleMap: defaultStyleMap, schemaMap: defaultSchemaMap });
导出实例化类

实例化后便可以导出使用了。

export default theme;

同时你也可以导出主题类型:

export type ThemeType = typeof theme;

它可以在你只需要类型提示时使用。

到现在为止,你就拥有了初始化创建主题,它具有良好的开发类型提示,这使你在使用 theme.schema API 时具有良好的类型提示。

编程式

使用编程式 API 创建主题,具有更好的可编程性。

你可以使用它:动态创建方案动态创建样式动态修改变量、...

甚至还可以将主题的控制权交给用户。

实例化

与初始化创建不同的是,编程式创建需要先进行 Colorfully 的实例化。

const theme = new Colorfully();
导出实例化类

将实例化类进行导出。

export default theme
创建样式

同样的我们以色彩样式示例:

import theme from "./theme";

theme.style.create('色彩', 'color', {
  light: {
      name: '浅色',
      code: 'light',
      variables: {
          '--color-1': {
              name: '',
              code: '--color-1',
              value: '#e6fffb'
          },
          '--color-2': {
              name: '',
              code: '--color-2',
              value: '#b5f5ec'
          },
          ...{ /* more */}
      }
  },
  dark: {
      name: '深色',
      code: 'dark',
      variables: {
          '--color-1': {
              name: '',
              code: '--color-1',
              value: '#112123'
          },
          '--color-2': {
              name: '',
              code: '--color-2',
              value: '#113536'
          },
          ...{ /* more */}
      }
  }
})

同样的,你还可以创建更多,如:shadowspace、...

创建方案

接下来使用 API 创建一个方案:

import theme from "./theme";

theme.schema.create('浅色主题', 'light', { color: 'light', shadow: 'default', space: 'default' });

你还可以创建更多,如:darkSchemadefaultSchema、...

配置化

与其他使用方案不同的是,在配置化中,你可以很灵活的自由导出导出主题配置。

你可以用它实现:远程加载主题动态导入主题主题分享、...

实例化

同样与初始化创建不同的是,配置化创建需要先进行 Colorfully 的实例化。

const theme = new Colorfully();
导出实例化类

将实例化类进行导出。

export default theme
导入配置(创建样式及方案)

使用 theme.import API 可以统一导入样式及方案。

import theme from "./theme";

const config: ColorfullyConfig = {
  styles: [
    {
      name: '色彩',
      code: 'color',
      types: [
        {
          name: '浅色',
          code: 'light',
          variables: [{ name: '', code: '--color-1', value: '#e6fffb' }]
        }
        /* ... */
      ]
    }
    /* ... */
  ],
  schemas: [
    { name: '浅色主题', code: 'light', map: { color: 'light', shadow: 'default', space: 'default' } }
    /* ... */
  ]
};

theme.import(config);
导出配置

使用 theme.export API 可以统一导出样式及方案。

import theme from "./theme";

const config = theme.export()

引入使用

在使用主题时你可以使用 theme.use 方法指定主题方案。

import { theme } from "./theme"

theme.use('default')
使用参数

如果你在 new Colorfully 时未传入 UseParams 或你使用的是主题包时,可以使用 theme.updateDefaultUseParams 更新你的 UseParams

UseParams 具体参数如下:

/**
 * 使用参数
 */
export interface UseParams<StyleMap extends Record<string, CSSStyle<any, any, any>>> {
    /**
     * 主题根节点
     * @default html
     * @description 也是选择器的挂载点。
     */
    root?: HTMLElement;
    /**
     * 模式
     * @default "css"
     * @description ‘css’ 模式也就是 'all in' 所有 css 都会提前生成挂载,'js' 模式也就是 'Import on demand' 所有的 css 都会被 js 按需导入。
     */
    mode?: 'css' | 'js';
    /**
     * 选择器模式
     * @description 决定挂载器是属性模式还是类名模式。
     */
    selectorMode?: 'attr' | 'class';
    /**
     * 自定义挂载
     * @description 对不支持 dom 操作的情况提供自定义渲染支持。
     */
    customMount?: {
        /**
         * 选择器
         * @param themeMap { groupCode: typeCode }
         */
        selector: (themeMap: Record<string, string>) => void;
        /**
         * 样式
         * @param styleList 样式列表
         */
        style: (styleList: {
            code: string;
            css: string;
            /**
             * mode === 'js' 时才会有
             */
            variables?: Array<CSSStyleVariable>;
        }[], 
        /**
         * mode === 'js' 时才会有
         */
        styleMap?: ExtractVariableMap<StyleMap>) => void;
    };
}

通过上述属性达到更高的兼容性以及扩展性。

场景

接下来给出不同场景下的,解决方案。

对接组件库

Q:在我们的日常开发中,避免不了使用各式各样的组件库,那么我们如何快速接入呢?

A:因为设计原因,我们的选择器优先级大于大部分组件库的声明选择器,所以我们可以直接对接组件库的变量名,从而达到覆盖的目的。

我们就拿 antd 来进行示例。

我们首先在项目调试窗口查看其变量命名,分析过后得出其变量命名的规则。

同时在查阅 Ant Design设计语言后,分析出其使用 @ant-design/colors 快速生成十个色阶,并对各类行为规定了对应的色阶号。

由此可得:

import { generate } from "@ant-design/colors"

const getAntdColor = ({ color, name, dark, emitColors }: { color: string, name: string, dark?: boolean, emitColors?: boolean }) => {
  const colors = generate(color, { "theme": dark ? "dark" : "default" })
  const alias = [{ name: '', value: 6 }, { name: 'hover', value: 5 }, { name: 'active', value: 7 }, { name: 'outline', value: 1 }]
  const aliasMap = alias.reduce((map, item) => {
    const code = `--ant-${name}-color${item.name ? `-${item.name}` : ''}`
    const value = colors[item.value - 1]
    map[code] = {
      name: '',
      code,
      value
    }
    return map
  }, {} as Record<string, any>)

  return {
    ...aliasMap, ...(emitColors ? colors.reduce((map, item, index) => {
      const code = `--ant-${name}-${index + 1}`
      const value = item
      map[code] = {
        name: '',
        code,
        value
      }
      return map
    }, {} as Record<string, any>) : {})
  }
}

有了这个方法我们就可以快速的创建与其对应的变量。

export const color = new CSSStyle('色彩', 'color', {
  light: {
    name: '浅色',
    code: 'light',
    variables: {
      ...getAntdColor({ "color": '#13c2c2', "name": 'primary', "emitColors": true }),
      ...getAntdColor({ "color": '#1890ff', "name": 'info' }),
      ...getAntdColor({ "color": '#52c41a', "name": 'success' }),
      ...getAntdColor({ "color": '#faad14', "name": 'warning' }),
      ...getAntdColor({ "color": '#ff4d4f', "name": 'error' })
    }
  },
  dark: {
    name: '深色',
    code: 'dark',
    variables: {
      ...getAntdColor({ "color": '#13a8a8', "name": 'primary', "emitColors": true, dark: true }),
      ...getAntdColor({ "color": '#177ddc', "name": 'info', dark: true }),
      ...getAntdColor({ "color": '#49aa19', "name": 'success', dark: true }),
      ...getAntdColor({ "color": '#d89614', "name": 'warning', dark: true }),
      ...getAntdColor({ "color": '#a61d24', "name": 'error', dark: true })
    }
  }
});

这样在项目中就可以自由的使用 Colorfully 的功能了。

自适应主题

Q:什么是自适应主题呢?

A:自适应是指根据环境因素的不同有不同的展现。

接下来我们来看一个实例,你一定见过跟随系统的明暗主题,即在系统中设置浅色或深色时界面随之改变。

Windows 下的明暗设置可以在其设置的颜色设置中找到(选择模式)进行设置。

那么我们如何在代码中获取到这个设置呢?

CSS 中,我们可以通过 @media 获取到,也就是当 @media (prefers-color-scheme: light) 时为浅色模式,为 @media (prefers-color-scheme: dark) 时为深色模式。

那么我们就可以思考如何将它集成进我们的 color CSSStyle 中。

首先我们添加一个 color 的类型为 default 表示跟随系统的变化而变化,然后我们继承类 CSSStyle 改写其 parcel 方法。

class ColorCSSStyle<T extends string> extends CSSStyle<T> {
  parcel(type: T | (string & Record<never, never>), value: string) {
    if (type === 'default') {
      return this.getAll()
        .filter(type => type.code !== 'default')
        .map(
          type => `@media (prefers-color-scheme: ${type.code}) {
*[ data-theme-color = 'default' ] {
${this.toTypeString(type.code)}
}
}`
        )
        .join('\n');
    }

    return super.parcel(type, value);
  }
}

export const color = new ColorCSSStyle('色彩', 'color', {
	default: { name: '默认', code: 'default', variables: {} },
    light: {/*...*/},
    dark: {/*...*/},
})

虽然 defaultvariables 值为空对象,但是这并不影响其 CSS 样式的生成,因为我们已经改写了 colorparcel 方法,使其检测到类型为 default 时,自动生成自适应的 @media 选择器。

以上方案在不复杂的场景下适用。

但是由于配置的自由度比较高,所以在我们重新创建 CSSStyle 后,我们使用的并不是 ColorCSSStyle,这会带来一定的问题。

因此我们内置了它,当 typedefault 时,且 variables{},我们将会在内部为你自动提供媒体查询功能。

可直接写为:

export const color = new CSSStyle('色彩', 'color', {
	default: { name: '默认', code: 'default', variables: {} },
    light: {/*...*/},
    dark: {/*...*/},
})

范围主题

Q:什么是范围主题?

A:这其实就是字面意思,想象一下我们并不想让部分元素跟随主题变化,而是拥有其独特的属性存在,这就是范围主题的诞生。

我们只需要给使用范围指定主题的元素加上 data-theme-color 属性并给到指定值 dark 就可以指定其 color 样式为深色。

Q:这是基础的控制,那么我们可以像 theme 一样管理它吗?

A:也是可以的,你只需要初始化一个新的 Colorfully 并且传递其 root 参数为你需要使用范围主题的元素就可以了。

如果你不想再重新设置一遍,也可以直接从 theme 中使用 theme.derive 方法派生出一个实例,同样的你也需要指定其 root 参数。

theme.derive({ root: dom });

支持小程序

Q:小程序如何使用 Colorfully 呢?

A:由于小程序不支持操作 dom,所以我们只能通过 data 配合自定义渲染的形式进行挂载。

这里使用微信小程序简单示例一下。

首先我们创建 selectorstyle 两个 data,并将他们分别挂载在标签的 class 以及 style 属性上,然后通过 theme.updateDefaultUseParams 进行自定义渲染配置。

onLoad() {
    let _this = this

    theme.updateDefaultUseParams({
        "mode": 'js',
        "selectorMode": 'class',
        "customMount": {
            "selector": (themeMap) => {
                _this.selector = Object.keys(themeMap).map(code => `data-theme-${code}-${themeMap[code]}`).join(' ')
                _this.$apply();
            },
            "style": (styleList) => {
                let css = ''
                styleList.forEach(style => {
                    style.variables.forEach(variable => {
                        css += `${variable.code}: ${variable.value};`
                    })
                })
                _this.style = css
                _this.$apply();
            }
        }
    })

    theme.use('light')
}

这样我们就算是接入了 Colorfully 的主题管理系统了。

对于小程序的监听系统主题改变,可以使用 wx.onThemeChange 方法自行实现。

支持 React Native

QReact Native 如何使用 Colorfully 呢?

A:在 React Native 中并没有 CSS,因为它采用的是类似CSS的概念,且只能从组件的 style 注入。我们可以将 Colorfully 的主题通过变量这个载体传入。

接下来让我们看看如何实现吧。

我们首先把 style 交给 Mobx 进行状态管理:

import theme from '@pin-co/theme';
import { makeAutoObservable, runInAction } from 'mobx';

class ThemeStore {
  // 推断 styleMap 类型
  style = {} as Exclude<
    Parameters<Exclude<Parameters<typeof theme.updateDefaultUseParams>['0']['customMount'], undefined>['style']>['1'],
    undefined
  >;

  constructor() {
    makeAutoObservable(this);
  }

  init() {
    theme.updateDefaultUseParams({
      mode: 'js',
      customMount: {
        selector: () => null,
        style: (_, styleMap) => {
          if (styleMap)
            runInAction(() => {
              this.style = styleMap;
            });
        }
      }
    });

    theme.use('light');
  }
}

const themeStore = new ThemeStore();

export default themeStore;

我们在声明 style 时,给到它类型推断,这样你在使用 style 时就可以得到类型提示了。

然后在组件中使用它:

import React, { useEffect } from 'react';
import { useColorScheme } from 'react-native';
import theme from '@xxx/theme';
import themeStore from './store/theme';

// 初始化主题
themeStore.init();

export function App() {
  const systemTheme = useColorScheme() ?? 'light';

  useEffect(() => {
    // 自适应系统深浅色主题
    theme.use(systemTheme);
  }, [systemTheme]);

  return (
    <View>
      {/* 使用主题 */}
      <Observer>
          {() => 
          	<Text style={{ color: themeStore.style.color['--color-text-primary'] }}>
               测试
           	</Text>
          }
      </Observer>
    </View>
  );
}

registerRootComponent(App);

这样就可以了,可以开心的使用 Colorfully 了。

你一定很好奇为什么要这样写 themeStore.style.color['--color-text-primary'],其中 colorkey 是你自己定义的。

你可以在定义主题时这样写:

import { CSSStyle } from 'colorfully';

export const color = new CSSStyle('色彩', 'color', {
    light: {
        name: '浅色',
        code: 'light',
        variables: {
            textPrimary: {
                name: '',
                code: '--color-text-primary',
                value: '#262626'
            },
            ...{ /* more */}
        }
    },
    dark: {
        name: '深色',
        code: 'dark',
        variables: {
            textPrimary: {
                name: '',
                code: '--color-text-primary',
                value: '#dbdbdb'
            },
            ...{ /* more */}
        }
    }
})

那么对于的使用:

<Observer>
    {() => 
    <Text style={{ color: themeStore.style.color.textPrimary }}>
         测试
     </Text>
    }
</Observer>

这样看起来顺眼多了对吧。

再告诉你一个小技巧,按照以下形式写将会得到类型提示的描述:

import { CSSStyle } from 'colorfully';

/**
 * 样式变量
 */
export interface IStyleVariable<T = string> {
  /**
   * 名称
   */
  name: string;
  /**
   * 代码
   */
  code: string;
  /**
   * 值
   */
  value: T;
}

/**
 * 颜色变量
 */
interface IColorVariable {
  /**
   * 主要文本
   */
  textPrimary: IStyleVariable;
  [k: string]: any;
}

export const color = new CSSStyle('色彩', 'color', {
  light: {
    name: '浅色',
    code: 'light',
    variables: {
      textPrimary: {
        name: '',
        code: '--color-text-primary',
        value: '#262626'
      }
    } as IColorVariable
  },
  dark: {
    name: '深色',
    code: 'dark',
    variables: {
      textPrimary: {
        name: '',
        code: '--color-text-primary',
        value: '#dbdbdb'
      }
    } as IColorVariable
  }
});

这样约束 variables 的同时,你也可以得到类型提示的描述了。

主题包

当你有多个项目时,如何复用就显得尤为重要了。

你可以将你预设好的主题,打包发布到 NPM 方便其他项目复用。

你只需包含基于 CommonJS 规范的 lib/index.js 并提供默认导出 theme 以供接下来的智能提示使用即可。

智能提示

既然创建了主题,那么必然有使用主题的时候。

我们在使用 CSS 变量的时候,是没有智能提示的,那也就代表着我们处于一个摸黑的状态。

那么如何打破这个黑暗就显得尤为重要了。

我们只需要安装 VSCode 中的 CSS Smart 插件即可解决这个问题。

具体查看 CSS Smart 中的介绍使用。

我们在插件中实现了主题包的智能提示解决方案,也就是动态执行 lib/index.js 得到其所有变量值来进行提示。

我们也会考虑后续执行本地暴露文件的可能性。

接口

这里不介绍具体 API 了,TypeScript 开发的懂得都懂,且以上示例比较全面。

ThemeParameters

/**
 * 主题参数
 */
export interface ThemeParameters<StyleMap, SchemaMap> {
  /**
   * 样式配置图
   */
  styleMap?: StyleMap;
  /**
   * 主题配置图
   */
  schemaMap?: SchemaMap;
  /**
   * 默认使用参数
   */
  defaultUseParams?: UseParams;
}

ColorfullyConfig

配置格式。

/**
 * 主题配置
 */
export interface ColorfullyConfig {
  /**
   * 样式
   */
  styles: Array<{
    /**
     * 名称
     */
    name: string;
    /**
     * 代码
     */
    code: string;
    /**
     * 类型
     */
    types: Array<{
      /**
       * 名称
       */
      name: string;
      /**
       * 代码
       */
      code: string;
      /**
       * 变量
       */
      variables: Array<{
        /**
         * 名称
         */
        name: string;
        /**
         * 代码
         */
        code: string;
        /**
         * 值
         */
        value: string;
      }>;
    }>;
  }>;
  /**
   * 方案
   */
  schemas: Array<{
    /**
     * 名称
     */
    name: string;
    /**
     * 代码
     */
    code: string;
    /**
     * { 样式 : 类型 }
     */
    map: Record<string, string>;
  }>;
}

Keywords

FAQs

Package last updated on 15 Mar 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc