Dojo 应用程序支持主题
Dojo 应用程序需要一种方法,来为所有部件展示一致的外观,这样用户就可以整体地把握和使用应用程序功能,而不是认为将东拼西凑的元素混搭在网页中。这通常要根据公司或产品的营销主题来指定颜色、布局或字体等实现的。
制作支持主题的部件
考虑让部件支持主题需要做两方面的准备:
- 需要为部件的工厂函数注入
theme
中间件,const factory = create({ theme })
- 渲染部件时,应该使用
theme.classes(css)
返回的一个或多个部件样式类。
按惯例,当开发的部件需要分发时,还需要考虑第三点要求(Dojo 部件库中的部件都遵循此约定):
- 部件的 VDOM 根节点(即部件渲染后的最外围节点)应该包含一个名为
root
的样式类。这样当在自定义主题中覆写第三方可主题化部件的样式时,就能以一致的方式定位到顶层节点。
theme
中间件是从 @dojo/framework/core/middleware/theme
模块中导入的。
theme.classes
方法
theme.classes
将部件的 CSS 类名转换应用程序或部件的主题类名。
theme.classes<T extends ClassNames>(css: T): T;
- 注意事项 1: 主题的重写只在 CSS 类一级,而不是 CSS 类中的单个样式属性。
- 注意事项 2: 如果当前激活的主题没有重写给定的样式类,则部件会退而使用该类的默认样式属性。
- 注意事项 3: 如果当前激活的主题的确重写了给定的样式类,则 只会 将主题中指定的 CSS 属性应用到部件上。例如,如果部件的默认样式类包含 10 个 CSS 属性,但是当前的主题只指定了一个,则部件渲染时只会使用这一个 CSS 属性,并丢掉在主题中未重写的其他 9 个属性。
theme
中间件属性
theme
(可选)- 如果指定,则所提供的主题会重写部件使用的任何主题,并且优先于应用程序的默认主题,以及应用程序中切换的任何其他主题。
classes
(可选)- 在为部件传入外部样式类一节有详细描述。
variant
(可选)- 从当前的主题变体中返回
root
类。 - 应该应用到部件的根节点上。
- 从当前的主题变体中返回
可主题化部件示例
下面是一个可主题化部件的 CSS 模块文件:
src/styles/MyThemeableWidget.m.css
/* requirement 4, i.e. this widget is intended for wider distribution,
therefore its outer-most VDOM element uses the 'root' class: */
.root {
font-family: sans-serif;
}
/* widgets can use any variety of ancillary CSS classes that are also themeable */
.myWidgetExtraThemeableClass {
font-variant: small-caps;
}
/* extra 'fixed' classes can also be used to specify a widget's structural styling, which is not intended to be
overridden via a theme */
.myWidgetStructuralClass {
font-style: italic;
}
在相应的可主题化的部件中使用这些样式:
src/widgets/MyThemeableWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import theme from '@dojo/framework/core/middleware/theme';
import * as css from '../styles/MyThemeableWidget.m.css';
/* requirement 1: */
const factory = create({ theme });
export default factory(function MyThemeableWidget({ middleware: { theme } }) {
/* requirement 2 */
const { root, myWidgetExtraThemeableClass } = theme.classes(css);
return (
<div
classes={[
/* requirement 3: */
root,
myWidgetExtraThemeableClass,
css.myWidgetExtraThemeableClass,
theme.variant()
]}
>
Hello from a themed Dojo widget!
</div>
);
});
使用多个 CSS 模块
部件也能导入和引用多个 CSS 模块,除了本指南的其它部分介绍的基于 CSS 的方法(CSS 自定义属性 和 CSS 模块化组合功能)之外,这提供了另一种通过 TypeScript 代码来提取和复用公共样式属性的方法。
扩展上述示例:
src/styles/MyThemeCommonStyles.m.css
.commonBase {
border: 4px solid black;
border-radius: 4em;
padding: 2em;
}
src/widgets/MyThemeableWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import theme from '@dojo/framework/core/middleware/theme';
import * as css from '../styles/MyThemeableWidget.m.css';
import * as commonCss from '../styles/MyThemeCommonStyles.m.css';
const factory = create({ theme });
export default factory(function MyThemeableWidget({ middleware: { theme } }) {
const { root } = theme.classes(css);
const { commonBase } = theme.classes(commonCss);
return (
<div classes={[root, commonBase, css.myWidgetExtraThemeableClass, theme.variant()]}>
Hello from a themed Dojo widget!
</div>
);
});
重写部件实例的主题
部件的使用者可以将一个有效的主题传给部件实例的 theme
属性,来重写特定部件实例的主题。当需要在应用程序的不同部分以多种方式显示给定的部件时,这个功能就能派上用场。
例如,在可主题化部件示例的基础上构建:
src/themes/myTheme/styles/MyThemeableWidget.m.css
.root {
color: blue;
}
src/themes/myThemeOverride/theme.ts
import * as myThemeableWidgetCss from './styles/MyThemeableWidget.m.css';
export default {
'my-app/MyThemeableWidget': myThemeableWidgetCss
};
src/widgets/MyApp.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import MyThemeableWidget from './src/widgets/MyThemeableWidget.tsx';
import * as myThemeOverride from '../themes/myThemeOverride/theme.ts';
const factory = create();
export default factory(function MyApp() {
return (
<div>
<MyThemeableWidget />
<MyThemeableWidget theme={myThemeOverride} />
</div>
);
});
此处,渲染了两个 MyThemeableWidget
实例,如果指定了应用程序范围的主题,则第一个部件会使用此主题,否则使用部件的默认样式。相比之下,第二个部件始终使用 myThemeOverride
中定义的主题。
为部件传入额外的样式
主题机制提供了一种简便的方式,为应用程序中的每个部件统一应用自定义样式,但当用户希望为给定的部件实例应用额外的样式时,在这种场景下主题机制就不够灵活。
可以通过可主题化部件的 classes
属性来传入额外的样式类。这些样式类是追加的,不会重写部件已有的样式类,它们的目的是对已经存在的样式进行细粒度的调整。提供的每一组额外的样式类都需要按照两个级别的 key 进行分组:
- 合适的部件主题 key,用于指定应用样式类的部件,包括其中的任何子部件。
- 小部件使用的某个已存在的 CSS 类,部件使用者可以在单个 DOM 元素上扩展样式,一个部件上可扩展多个样式。
例如,额外的样式类属性的类型定义为:
type ExtraClassName = string | null | undefined | boolean;
interface Classes {
[widgetThemeKey: string]: {
[baseClassName: string]: ExtraClassName[];
};
}
作为一个提供额外样式类的示例,下面调整 Dojo combobox 实例,以及其中的子部件 text input。此操作会将 combobox 使用的 text input 控件的背景色以及其自身面板的背景色改为蓝色。combobox 控件面板中的下拉箭头也会变为红色:
src/styles/MyComboBoxStyleTweaks.m.css
.blueBackground {
background-color: blue;
}
.redArrow {
color: red;
}
src/widgets/MyWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import ComboBox from '@dojo/widgets/combobox';
import * as myComboBoxStyleTweaks from '../styles/MyComboBoxStyleTweaks.m.css';
const myExtraClasses = {
'@dojo/widgets/combobox': {
controls: [myComboBoxStyleTweaks.blueBackground],
trigger: [myComboBoxStyleTweaks.redArrow]
},
'@dojo/widgets/text-input': {
input: [myComboBoxStyleTweaks.blueBackground]
}
};
const factory = create();
export default factory(function MyWidget() {
return (
<div>
Hello from a tweaked Dojo combobox!
<ComboBox classes={myExtraClasses} results={['foo', 'bar']} />
</div>
);
});
注意,部件的作者负责显式地将 classes
属性传给所有的要使用样式类的子部件,因为 Dojo 本身无法将这个属性注入给或自动传给子部件。
制作支持主题的应用程序
要为应用程序中所有可主题化的部件指定一个主题,可在应用程序顶层部件中使用 theme
中间件中的 theme.set
API。要设置默认的或初始的主题,则在调用 theme.set
之前要先使用 theme.get
进行确认。
例如,为应用程序设置一个初始主题:
src/App.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import theme from '@dojo/framework/core/middleware/theme';
import myTheme from '../themes/MyTheme/theme';
const factory = create({ theme });
export default factory(function App({ middleware: { theme }}) {
// if the theme isn't set, set the default theme
if (!theme.get()) {
theme.set(myTheme);
}
return (
// the application's widgets
);
});
有关导入的 myTheme
结构说明,请参考编写主题。
请注意,使用可主题化的部件时,如果没有显示指定主题(例如,没有使用 theme.set
设置一个默认主题,也没有显式地重写部件实例的主题或样式类),则每个部件都使用默认的样式规则。
如果使用一个完全独立分发的主题(/learn/styling/working-with-themes#distributing-themes),应用程序还需要将囊括主题的 index.css
文件集成到自身的样式中来。在项目的 main.css
文件中导入。
src/main.css
@import '@{myThemePackageName}/{myThemeName}/index.css';
与之相比,另一种使用外部构建主题的部分内容的方法是通过主题组合功能(/learn/styling/working-with-themes#composing-off-dojo-themes)实现的。
更改当前激活的主题
theme
中间件中的 .set(theme)
函数用于在整个应用程序级别更改当前激活的主题。为 .set
传入所需的主题,这将让应用程序树中所有可主题化的部件失效,并使用新的主题重新渲染。
src/widgets/ThemeSwitcher.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import theme from '@dojo/framework/core/middleware/theme';
import myTheme from '../themes/MyTheme/theme';
import alternativeTheme from '../themes/MyAlternativeTheme/theme';
const factory = create({ theme });
export default factory(function ThemeSwitcher({ middleware: { theme } }) {
return (
<div>
<button
onclick={() => {
theme.set(myTheme);
}}
>
Use Default Theme
</button>
<button
onclick={() => {
theme.set(alternativeTheme);
}}
>
Use Alternative Theme
</button>
</div>
);
});