Dojo 应用程序支持主题

    考虑让部件支持主题需要做两方面的准备:

    1. 需要为部件的工厂函数注入 theme 中间件,const factory = create({ theme })
    2. 渲染部件时,应该使用 theme.classes(css) 返回的一个或多个部件样式类。

    按惯例,当开发的部件需要分发时,还需要考虑第三点要求(Dojo 部件库中的部件都遵循此约定):

    1. 部件的 VDOM 根节点(即部件渲染后的最外围节点)应该包含一个名为 root 的样式类。这样当在自定义主题中覆写第三方可主题化部件的样式时,就能以一致的方式定位到顶层节点。

    theme 中间件是从 @dojo/framework/core/middleware/theme 模块中导入的。

    theme.classes 将部件的 CSS 类名转换应用程序或部件的主题类名。

    • 注意事项 1: 主题的重写只在 CSS 类一级,而不是 CSS 类中的单个样式属性。
    • 注意事项 2: 如果当前激活的主题没有重写给定的样式类,则部件会退而使用该类的默认样式属性。
    • 注意事项 3: 如果当前激活的主题的确重写了给定的样式类,则 只会 将主题中指定的 CSS 属性应用到部件上。例如,如果部件的默认样式类包含 10 个 CSS 属性,但是当前的主题只指定了一个,则部件渲染时只会使用这一个 CSS 属性,并丢掉在主题中未重写的其他 9 个属性。

    theme 中间件属性

    • theme (可选)
    • classes (可选)
    • variant (可选)
      • 从当前的主题变体中返回 root 类。
      • 应该应用到部件的根节点上。

    下面是一个可主题化部件的 CSS 模块文件:

    1. /* requirement 4, i.e. this widget is intended for wider distribution,
    2. therefore its outer-most VDOM element uses the 'root' class: */
    3. .root {
    4. font-family: sans-serif;
    5. }
    6. /* widgets can use any variety of ancillary CSS classes that are also themeable */
    7. .myWidgetExtraThemeableClass {
    8. font-variant: small-caps;
    9. }
    10. /* extra 'fixed' classes can also be used to specify a widget's structural styling, which is not intended to be
    11. overridden via a theme */
    12. .myWidgetStructuralClass {
    13. font-style: italic;
    14. }

    在相应的可主题化的部件中使用这些样式:

    src/widgets/MyThemeableWidget.tsx

    1. import { create, tsx } from '@dojo/framework/core/vdom';
    2. import theme from '@dojo/framework/core/middleware/theme';
    3. import * as css from '../styles/MyThemeableWidget.m.css';
    4. /* requirement 1: */
    5. const factory = create({ theme });
    6. export default factory(function MyThemeableWidget({ middleware: { theme } }) {
    7. /* requirement 2 */
    8. const { root, myWidgetExtraThemeableClass } = theme.classes(css);
    9. return (
    10. <div
    11. classes={[
    12. /* requirement 3: */
    13. root,
    14. myWidgetExtraThemeableClass,
    15. theme.variant()
    16. ]}
    17. >
    18. Hello from a themed Dojo widget!
    19. </div>
    20. );
    21. });

    使用多个 CSS 模块

    部件也能导入和引用多个 CSS 模块,除了本指南的其它部分介绍的基于 CSS 的方法(CSS 自定义属性 和 )之外,这提供了另一种通过 TypeScript 代码来提取和复用公共样式属性的方法。

    扩展上述示例:

    src/styles/MyThemeCommonStyles.m.css

    1. .commonBase {
    2. border: 4px solid black;
    3. padding: 2em;
    4. }

    重写部件实例的主题

    部件的使用者可以将一个传给部件实例的 theme 属性,来重写特定部件实例的主题。当需要在应用程序的不同部分以多种方式显示给定的部件时,这个功能就能派上用场。

    例如,在的基础上构建:

    1. .root {
    2. color: blue;
    3. }

    src/themes/myThemeOverride/theme.ts

    1. import * as myThemeableWidgetCss from './styles/MyThemeableWidget.m.css';
    2. export default {
    3. 'my-app/MyThemeableWidget': myThemeableWidgetCss
    4. };

    src/widgets/MyApp.tsx

    1. import { create, tsx } from '@dojo/framework/core/vdom';
    2. import MyThemeableWidget from './src/widgets/MyThemeableWidget.tsx';
    3. import * as myThemeOverride from '../themes/myThemeOverride/theme.ts';
    4. const factory = create();
    5. export default factory(function MyApp() {
    6. return (
    7. <div>
    8. <MyThemeableWidget />
    9. <MyThemeableWidget theme={myThemeOverride} />
    10. </div>
    11. );
    12. });

    此处,渲染了两个 MyThemeableWidget 实例,如果指定了应用程序范围的主题,则第一个部件会使用此主题,否则使用部件的默认样式。相比之下,第二个部件始终使用 myThemeOverride 中定义的主题。

    主题机制提供了一种简便的方式,为应用程序中的每个部件统一应用自定义样式,但当用户希望为给定的部件实例应用额外的样式时,在这种场景下主题机制就不够灵活。

    可以通过可主题化部件的 classes 属性来传入额外的样式类。这些样式类是追加的,不会重写部件已有的样式类,它们的目的是对已经存在的样式进行细粒度的调整。提供的每一组额外的样式类都需要按照两个级别的 key 进行分组:

    1. 合适的,用于指定应用样式类的部件,包括其中的任何子部件。
    2. 小部件使用的某个已存在的 CSS 类,部件使用者可以在单个 DOM 元素上扩展样式,一个部件上可扩展多个样式。

    例如,额外的样式类属性的类型定义为:

    作为一个提供额外样式类的示例,下面调整 Dojo combobox 实例,以及其中的子部件 。此操作会将 combobox 使用的 text input 控件的背景色以及其自身面板的背景色改为蓝色。combobox 控件面板中的下拉箭头也会变为红色:

    src/styles/MyComboBoxStyleTweaks.m.css

    1. .blueBackground {
    2. background-color: blue;
    3. }
    4. .redArrow {
    5. color: red;
    6. }
    1. import { create, tsx } from '@dojo/framework/core/vdom';
    2. import ComboBox from '@dojo/widgets/combobox';
    3. import * as myComboBoxStyleTweaks from '../styles/MyComboBoxStyleTweaks.m.css';
    4. const myExtraClasses = {
    5. '@dojo/widgets/combobox': {
    6. controls: [myComboBoxStyleTweaks.blueBackground],
    7. trigger: [myComboBoxStyleTweaks.redArrow]
    8. },
    9. '@dojo/widgets/text-input': {
    10. }
    11. };
    12. const factory = create();
    13. export default factory(function MyWidget() {
    14. return (
    15. <div>
    16. Hello from a tweaked Dojo combobox!
    17. </div>
    18. );
    19. });

    注意,部件的作者负责显式地将 classes 属性传给所有的要使用样式类的子部件,因为 Dojo 本身无法将这个属性注入给或自动传给子部件。

    制作支持主题的应用程序

    要为应用程序中所有可主题化的部件指定一个主题,可在应用程序顶层部件中使用 theme 中间件中的 theme.set API。要设置默认的或初始的主题,则在调用 theme.set 之前要先使用 theme.get 进行确认。

    例如,为应用程序设置一个初始主题:

    src/App.tsx

    1. import { create, tsx } from '@dojo/framework/core/vdom';
    2. import theme from '@dojo/framework/core/middleware/theme';
    3. import myTheme from '../themes/MyTheme/theme';
    4. const factory = create({ theme });
    5. export default factory(function App({ middleware: { theme }}) {
    6. // if the theme isn't set, set the default theme
    7. if (!theme.get()) {
    8. theme.set(myTheme);
    9. }
    10. return (
    11. // the application's widgets
    12. );
    13. });

    有关导入的 myTheme 结构说明,请参考。

    请注意,使用可主题化的部件时,如果没有显示指定主题(例如,没有使用 theme.set 设置一个默认主题,也没有显式地重写部件实例的主题或样式类),则每个部件都使用默认的样式规则。

    如果使用一个完全独立分发的主题(/learn/styling/working-with-themes#distributing-themes),应用程序还需要将囊括主题的 index.css 文件集成到自身的样式中来。在项目的 main.css 文件中导入。

    src/main.css

    与之相比,另一种使用外部构建主题的部分内容的方法是通过主题组合功能(/learn/styling/working-with-themes#composing-off-dojo-themes)实现的。

    更改当前激活的主题

    theme 中间件中的 .set(theme) 函数用于在整个应用程序级别更改当前激活的主题。为 .set 传入所需的主题,这将让应用程序树中所有可主题化的部件失效,并使用新的主题重新渲染。

    src/widgets/ThemeSwitcher.tsx

    1. import { create, tsx } from '@dojo/framework/core/vdom';
    2. import theme from '@dojo/framework/core/middleware/theme';
    3. import myTheme from '../themes/MyTheme/theme';
    4. import alternativeTheme from '../themes/MyAlternativeTheme/theme';
    5. const factory = create({ theme });
    6. export default factory(function ThemeSwitcher({ middleware: { theme } }) {
    7. return (
    8. <div>
    9. <button
    10. onclick={() => {
    11. theme.set(myTheme);
    12. }}
    13. >
    14. Use Default Theme
    15. </button>
    16. <button
    17. onclick={() => {
    18. theme.set(alternativeTheme);
    19. }}
    20. >
    21. Use Alternative Theme
    22. </button>
    23. </div>
    24. });