diff --git a/.antd-tools.config.js b/.antd-tools.config.js index 6a229451ca..e5f3f34030 100644 --- a/.antd-tools.config.js +++ b/.antd-tools.config.js @@ -90,6 +90,7 @@ function finalizeDist() { buildThemeFile('default', defaultVars); buildThemeFile('dark', darkVars); buildThemeFile('compact', compactVars); + buildThemeFile('variable', {}); fs.writeFileSync( path.join(process.cwd(), 'dist', `theme.js`), ` @@ -125,8 +126,56 @@ module.exports = { } } +function isComponentStyle(file) { + return file.path.match(/style(\/|\\)index\.tsx/); +} + +function needTransformStyle(content) { + return content.includes('./index.less'); +} + module.exports = { compile: { + transformTSFile(file) { + if (isComponentStyle(file)) { + let content = file.contents.toString(); + + if (needTransformStyle(content)) { + const cloneFile = file.clone(); + + // Origin + content = content.replace('./index.less', './index-default.less'); + cloneFile.contents = Buffer.from(content); + + return cloneFile; + } + } + }, + transformFile(file) { + if (isComponentStyle(file)) { + const content = file.contents.toString(); + + if (needTransformStyle(content)) { + const cloneFile = file.clone(); + cloneFile.contents = Buffer.from( + [ + // Inject variable + '@root-entry-name: default', + // Point to origin file + "@import './index';", + ].join('\n\n'), + ); + cloneFile.path = cloneFile.path.replace('index.tsx', 'index-default.less'); + return cloneFile; + } + } + return []; + }, + lessConfig: { + modifyVars: { + 'root-entry-name': 'default', + }, + }, finalize: finalizeCompile, }, dist: { diff --git a/components/affix/__tests__/Affix.test.tsx b/components/affix/__tests__/Affix.test.tsx index 6a9dcaa17f..51a44031b4 100644 --- a/components/affix/__tests__/Affix.test.tsx +++ b/components/affix/__tests__/Affix.test.tsx @@ -206,11 +206,8 @@ describe('Affix Render', () => { // Mock trigger resize updateCalled.mockReset(); - const resizeObserverInstance: ReactWrapper< - HTMLAttributes, - unknown, - ResizeObserverImpl - > = affixMounterWrapper.find('ResizeObserver') as any; + const resizeObserverInstance: ReactWrapper = + affixMounterWrapper.find('ResizeObserver') as any; resizeObserverInstance .at(index) .instance() @@ -225,7 +222,7 @@ describe('Affix Render', () => { contentBoxSize: [], }, ], - ({} as unknown) as ResizeObserver, + {} as unknown as ResizeObserver, ); await sleep(20); diff --git a/components/button/style/index.less b/components/button/style/index.less index 2a5737b31f..38a7802cc8 100644 --- a/components/button/style/index.less +++ b/components/button/style/index.less @@ -207,19 +207,19 @@ } &-background-ghost&-primary { - .button-variant-ghost(@btn-primary-bg); + .button-variant-ghost(@btn-primary-bg, @btn-primary-bg, @primary-color-hover, @primary-color-active); } &-background-ghost&-danger { - .button-variant-ghost(@btn-danger-border); + .button-variant-ghost(@btn-danger-border, @btn-danger-border, @error-color-hover, @error-color-active); } &-background-ghost&-dangerous { - .button-variant-ghost(@btn-danger-border); + .button-variant-ghost(@btn-danger-border, @btn-danger-border, @error-color-hover, @error-color-active); } &-background-ghost&-dangerous&-link { - .button-variant-ghost(@btn-danger-border, transparent); + .button-variant-ghost(@btn-danger-border, transparent, @error-color-hover, @error-color-active); } &-two-chinese-chars::first-letter { diff --git a/components/button/style/mixin.less b/components/button/style/mixin.less index f34c991a87..5aa3322f37 100644 --- a/components/button/style/mixin.less +++ b/components/button/style/mixin.less @@ -11,134 +11,6 @@ border-radius: @border-radius; } -.button-disabled(@color: @btn-disable-color; @background: @btn-disable-bg; @border: @btn-disable-border) { - &[disabled] { - &, - &:hover, - &:focus, - &:active { - .button-color(@color; @background; @border); - - text-shadow: none; - box-shadow: none; - } - } -} - -.button-variant-primary(@color; @background) { - .button-color(@color; @background; @background); - - text-shadow: @btn-text-shadow; - box-shadow: @btn-primary-shadow; - - &:hover, - &:focus { - & when (@theme = dark) { - .button-color( - @color; ~`colorPalette('@{background}', 7) `; ~`colorPalette('@{background}', 7) ` - ); - } - & when not (@theme = dark) { - .button-color( - @color; ~`colorPalette('@{background}', 5) `; ~`colorPalette('@{background}', 5) ` - ); - } - } - - &:active { - & when (@theme = dark) { - .button-color( - @color; ~`colorPalette('@{background}', 5) `; ~`colorPalette('@{background}', 5) ` - ); - } - & when not (@theme = dark) { - .button-color( - @color; ~`colorPalette('@{background}', 7) `; ~`colorPalette('@{background}', 7) ` - ); - } - } - - .button-disabled(); -} - -.button-variant-other(@color; @background; @border) { - .button-color(@color; @background; @border); - - &:hover, - &:focus { - & when (@theme = dark) { - .button-color(@primary-5; @background; @primary-5); - } - & when not (@theme = dark) { - .button-color( - ~`colorPalette('@{btn-primary-bg}', 5) `; @background; - ~`colorPalette('@{btn-primary-bg}', 5) ` - ); - } - } - &:active { - & when (@theme = dark) { - .button-color(@primary-7; @background; @primary-7); - } - & when not (@theme = dark) { - .button-color( - ~`colorPalette('@{btn-primary-bg}', 7) `; @background; - ~`colorPalette('@{btn-primary-bg}', 7) ` - ); - } - } - .button-disabled(); -} -.button-variant-ghost(@color; @border: @color) { - .button-color(@color; null; @border); - text-shadow: none; - &:hover, - &:focus { - & when (@border = transparent) { - & when (@theme = dark) { - .button-color(~`colorPalette('@{color}', 7) `; null; transparent); - } - & when not (@theme = dark) { - .button-color(~`colorPalette('@{color}', 5) `; null; transparent); - } - } - & when not (@border = transparent) { - & when (@theme = dark) { - .button-color( - ~`colorPalette('@{color}', 7) `; null; ~`colorPalette('@{color}', 7) ` - ); - } - & when not (@theme = dark) { - .button-color( - ~`colorPalette('@{color}', 5) `; null; ~`colorPalette('@{color}', 5) ` - ); - } - } - } - &:active { - & when (@border = transparent) { - & when (@theme = dark) { - .button-color(~`colorPalette('@{color}', 5) `; null; transparent); - } - & when not (@theme = dark) { - .button-color(~`colorPalette('@{color}', 7) `; null; transparent); - } - } - & when not (@border = transparent) { - & when (@theme = dark) { - .button-color( - ~`colorPalette('@{color}', 5) `; null; ~`colorPalette('@{color}', 5) ` - ); - } - & when not (@theme = dark) { - .button-color( - ~`colorPalette('@{color}', 7) `; null; ~`colorPalette('@{color}', 7) ` - ); - } - } - } - .button-disabled(); -} .button-color(@color; @background; @border) { color: @color; border-color: @border; // a inside Button which only work in Chrome @@ -159,6 +31,161 @@ } } } + +.button-disabled(@color: @btn-disable-color; @background: @btn-disable-bg; @border: @btn-disable-border) { + &[disabled] { + &, + &:hover, + &:focus, + &:active { + .button-color(@color; @background; @border); + + text-shadow: none; + box-shadow: none; + } + } +} + +.button-variant-primary(@color; @background; @backgroundHover: yellow; @backgroundActive: yellow) { + .button-color(@color; @background; @background); + + text-shadow: @btn-text-shadow; + box-shadow: @btn-primary-shadow; + + &:hover, + &:focus { + & when (@theme = dark) { + .button-color( + @color; ~`colorPalette('@{background}', 7) `; ~`colorPalette('@{background}', 7) ` + ); + } + & when (not (@theme = dark) and not (@theme = variable)) { + .button-color( + @color; ~`colorPalette('@{background}', 5) `; ~`colorPalette('@{background}', 5) ` + ); + } + & when (@theme = variable) { + .button-color(@color; @backgroundHover; @backgroundHover); + } + } + + &:active { + & when (@theme = dark) { + .button-color( + @color; ~`colorPalette('@{background}', 5) `; ~`colorPalette('@{background}', 5) ` + ); + } + & when (not (@theme = dark) and not (@theme = variable)) { + .button-color( + @color; ~`colorPalette('@{background}', 7) `; ~`colorPalette('@{background}', 7) ` + ); + } + & when (@theme = variable) { + .button-color(@color; @backgroundActive; @backgroundActive); + } + } + + .button-disabled(); +} + +.button-variant-other(@color; @background; @border) { + .button-color(@color; @background; @border); + + &:hover, + &:focus { + & when (@theme = dark) { + .button-color(@primary-5; @background; @primary-5); + } + & when (not (@theme = dark) and not (@theme = variable)) { + .button-color( + ~`colorPalette('@{btn-primary-bg}', 5) `; @background; + ~`colorPalette('@{btn-primary-bg}', 5) ` + ); + } + & when (@theme = variable) { + .button-color(@primary-color-hover; @background; @primary-color-hover); + } + } + &:active { + & when (@theme = dark) { + .button-color(@primary-7; @background; @primary-7); + } + & when (not (@theme = dark) and not (@theme = variable)) { + .button-color( + ~`colorPalette('@{btn-primary-bg}', 7) `; @background; + ~`colorPalette('@{btn-primary-bg}', 7) ` + ); + } + & when (@theme = variable) { + .button-color(@primary-color-active; @background; @primary-color-active); + } + } + .button-disabled(); +} + +.button-variant-ghost(@color; @border; @borderHover: yellow; @borderActive: yellow) { + .button-color(@color; null; @border); + text-shadow: none; + &:hover, + &:focus { + & when (@border = transparent) { + & when (@theme = dark) { + .button-color(~`colorPalette('@{color}', 7) `; null; transparent); + } + & when (not (@theme = dark) and not (@theme = variable)) { + .button-color(~`colorPalette('@{color}', 5) `; null; transparent); + } + & when (@theme = variable) { + .button-color(@borderActive; transparent; transparent); + } + } + & when not (@border = transparent) { + & when (@theme = dark) { + .button-color( + ~`colorPalette('@{color}', 7) `; null; ~`colorPalette('@{color}', 7) ` + ); + } + & when (not (@theme = dark) and not (@theme = variable)) { + .button-color( + ~`colorPalette('@{color}', 5) `; null; ~`colorPalette('@{color}', 5) ` + ); + } + & when (@theme = variable) { + .button-color(@borderHover; transparent; @borderHover); + } + } + } + &:active { + & when (@border = transparent) { + & when (@theme = dark) { + .button-color(~`colorPalette('@{color}', 5) `; null; transparent); + } + & when (not (@theme = dark) and not (@theme = variable)) { + .button-color(~`colorPalette('@{color}', 7) `; null; transparent); + } + & when (@theme = variable) { + .button-color(@borderActive; transparent; transparent); + } + } + & when not (@border = transparent) { + & when (@theme = dark) { + .button-color( + ~`colorPalette('@{color}', 5) `; null; ~`colorPalette('@{color}', 5) ` + ); + } + & when (not (@theme = dark) and not (@theme = variable)) { + .button-color( + ~`colorPalette('@{color}', 7) `; null; ~`colorPalette('@{color}', 7) ` + ); + } + & when (@theme = variable) { + .button-color(@borderActive; transparent; @borderActive); + } + } + } + .button-disabled(); +} + .button-group-base(@btnClassName) { position: relative; display: inline-flex; @@ -252,11 +279,11 @@ } // primary button style .btn-primary() { - .button-variant-primary(@btn-primary-color; @btn-primary-bg); + .button-variant-primary(@btn-primary-color; @btn-primary-bg; @primary-color-hover; @primary-color-active); } // default button style .btn-default() { - .button-variant-other(@btn-default-color; @btn-default-bg; @btn-default-border); + .button-variant-other(@btn-default-color; @btn-default-bg; @btn-default-border; ); &:hover, &:focus, &:active { @@ -275,7 +302,7 @@ } // danger button style .btn-danger() { - .button-variant-primary(@btn-danger-color, @btn-danger-bg); + .button-variant-primary(@btn-danger-color, @btn-danger-bg, @error-color-hover, @error-color-active); } // danger default button style .btn-danger-default() { @@ -288,12 +315,15 @@ ` ); } - & when not (@theme = dark) { + & when (not (@theme = dark) and not (@theme = variable)) { .button-color( ~`colorPalette('@{error-color}', 5) `; @btn-default-bg; ~`colorPalette('@{error-color}', 5) ` ); } + & when (@theme = variable) { + .button-color(@error-color-hover, @btn-default-bg, @error-color-hover); + } } &:active { & when (@theme = dark) { @@ -302,12 +332,15 @@ ` ); } - & when not (@theme = dark) { + & when (not (@theme = dark) and not (@theme = variable)) { .button-color( ~`colorPalette('@{error-color}', 7) `; @btn-default-bg; ~`colorPalette('@{error-color}', 7) ` ); } + & when (@theme = variable) { + .button-color(@error-color-active, @btn-default-bg, @error-color-active); + } } .button-disabled(); } @@ -320,17 +353,23 @@ & when (@theme = dark) { .button-color(~`colorPalette('@{error-color}', 7) `; transparent; transparent); } - & when not (@theme = dark) { + & when (not (@theme = dark) and not (@theme = variable)) { .button-color(~`colorPalette('@{error-color}', 5) `; transparent; transparent); } + & when (@theme = variable) { + .button-color(@error-color-hover; transparent; transparent); + } } &:active { & when (@theme = dark) { .button-color(~`colorPalette('@{error-color}', 5) `; transparent; transparent); } - & when not (@theme = dark) { + & when (not (@theme = dark) and not (@theme = variable)) { .button-color(~`colorPalette('@{error-color}', 7) `; transparent; transparent); } + & when (@theme = variable) { + .button-color(@error-color-active; transparent; transparent); + } } .button-disabled(@disabled-color; transparent; transparent); } @@ -375,18 +414,24 @@ & when (@theme = dark) { .button-color(~`colorPalette('@{error-color}', 7) `; @btn-text-hover-bg; transparent); } - & when not (@theme = dark) { + & when (not (@theme = dark) and not (@theme = variable)) { .button-color(~`colorPalette('@{error-color}', 5) `; @btn-text-hover-bg; transparent); } + & when (@theme = variable) { + .button-color(@error-color-hover; @btn-text-hover-bg; transparent); + } } &:active { & when (@theme = dark) { .button-color(~`colorPalette('@{error-color}', 5) `; fadein(@btn-text-hover-bg, 1%); transparent); } - & when not (@theme = dark) { + & when (not (@theme = dark) and not (@theme = variable)) { .button-color(~`colorPalette('@{error-color}', 7) `; fadein(@btn-text-hover-bg, 1%); transparent); } + & when (@theme = variable) { + .button-color(@error-color-active; fadein(@btn-text-hover-bg, 1%); transparent); + } } .button-disabled(@disabled-color; transparent; transparent); } diff --git a/components/config-provider/__tests__/theme.test.ts b/components/config-provider/__tests__/theme.test.ts new file mode 100644 index 0000000000..cde86a2ede --- /dev/null +++ b/components/config-provider/__tests__/theme.test.ts @@ -0,0 +1,23 @@ +import { kebabCase } from 'lodash'; +import ConfigProvider from '..'; + +describe('ConfigProvider.Theme', () => { + const colorList = ['primaryColor', 'successColor', 'warningColor', 'errorColor', 'infoColor']; + + colorList.forEach(colorName => { + it(colorName, () => { + ConfigProvider.config({ + prefixCls: 'bamboo', + theme: { + [colorName]: '#0000FF', + }, + }); + + const styles: any[] = Array.from(document.querySelectorAll('style')); + const themeStyle = styles.find(style => style['rc-util-key'].includes('-dynamic-theme')); + expect(themeStyle).toBeTruthy(); + + expect(themeStyle.innerHTML).toContain(`--bamboo-${kebabCase(colorName)}: rgb(0, 0, 255)`); + }); + }); +}); diff --git a/components/config-provider/context.tsx b/components/config-provider/context.tsx index 25bb15ffb7..6f42ebeb45 100644 --- a/components/config-provider/context.tsx +++ b/components/config-provider/context.tsx @@ -4,6 +4,15 @@ import { Locale } from '../locale-provider'; import { SizeType } from './SizeContext'; import { RequiredMark } from '../form/Form'; +export interface Theme { + primaryColor?: string; + infoColor?: string; + successColor?: string; + processingColor?: string; + errorColor?: string; + warningColor?: string; +} + export interface CSPConfig { nonce?: string; } diff --git a/components/config-provider/cssVariables.tsx b/components/config-provider/cssVariables.tsx new file mode 100644 index 0000000000..96f3dde0a8 --- /dev/null +++ b/components/config-provider/cssVariables.tsx @@ -0,0 +1,97 @@ +/* eslint-disable import/prefer-default-export, prefer-destructuring */ + +import { updateCSS } from 'rc-util/lib/Dom/dynamicCSS'; +import { TinyColor } from '@ctrl/tinycolor'; +import { generate } from '@ant-design/colors'; +import { Theme } from './context'; + +const dynamicStyleMark = `-ant-${Date.now()}-${Math.random()}`; + +export function registerTheme(globalPrefixCls: string, theme: Theme) { + const variables: Record = {}; + + const formatColor = ( + color: TinyColor, + updater?: (cloneColor: TinyColor) => TinyColor | undefined, + ) => { + let clone = color.clone(); + clone = updater?.(clone) || clone; + return clone.toRgbString(); + }; + + const fillColor = (colorVal: string, type: string) => { + const baseColor = new TinyColor(colorVal); + const colorPalettes = generate(baseColor.toRgbString()); + + variables[`${type}-color`] = formatColor(baseColor); + variables[`${type}-color-disabled`] = colorPalettes[1]; + variables[`${type}-color-hover`] = colorPalettes[4]; + variables[`${type}-color-active`] = colorPalettes[7]; + variables[`${type}-color-outline`] = baseColor.clone().setAlpha(0.2).toRgbString(); + variables[`${type}-color-deprecated-bg`] = colorPalettes[1]; + variables[`${type}-color-deprecated-border`] = colorPalettes[3]; + }; + + // ================ Primary Color ================ + if (theme.primaryColor) { + fillColor(theme.primaryColor, 'primary'); + + const primaryColor = new TinyColor(theme.primaryColor); + const primaryColors = generate(primaryColor.toRgbString()); + + // Legacy - We should use semantic naming standard + primaryColors.forEach((color, index) => { + variables[`primary-${index + 1}`] = color; + }); + // Deprecated + variables['primary-color-deprecated-l-35'] = formatColor(primaryColor, c => c.lighten(35)); + variables['primary-color-deprecated-l-20'] = formatColor(primaryColor, c => c.lighten(20)); + variables['primary-color-deprecated-t-20'] = formatColor(primaryColor, c => c.tint(20)); + variables['primary-color-deprecated-t-50'] = formatColor(primaryColor, c => c.tint(50)); + variables['primary-color-deprecated-f-12'] = formatColor(primaryColor, c => + c.setAlpha(c.getAlpha() * 0.12), + ); + + const primaryActiveColor = new TinyColor(primaryColors[0]); + variables['primary-color-active-deprecated-f-30'] = formatColor(primaryActiveColor, c => + c.setAlpha(c.getAlpha() * 0.3), + ); + variables['primary-color-active-deprecated-d-02'] = formatColor(primaryActiveColor, c => + c.darken(2), + ); + } + + // ================ Success Color ================ + if (theme.successColor) { + fillColor(theme.successColor, 'success'); + } + + // ================ Warning Color ================ + if (theme.warningColor) { + fillColor(theme.warningColor, 'warning'); + } + + // ================= Error Color ================= + if (theme.errorColor) { + fillColor(theme.errorColor, 'error'); + } + + // ================= Info Color ================== + if (theme.infoColor) { + fillColor(theme.infoColor, 'info'); + } + + // Convert to css variables + const cssList = Object.keys(variables).map( + key => `--${globalPrefixCls}-${key}: ${variables[key]};`, + ); + + updateCSS( + ` + :root { + ${cssList.join('\n')} + } + `, + `${dynamicStyleMark}-dynamic-theme`, + ); +} diff --git a/components/config-provider/demo/theme.md b/components/config-provider/demo/theme.md new file mode 100644 index 0000000000..1066a9ed7e --- /dev/null +++ b/components/config-provider/demo/theme.md @@ -0,0 +1,633 @@ +--- +order: 5 +title: + zh-CN: 全局样式 + en-US: Global Theme +--- + +## zh-CN + +通过 css variable 修改全局主题色(你可以切换到组件页面查看更详细的样式展示),不支持 IE。自动生成的变量可能会根据设计调整,请勿直接依赖。详细配置请[点击查看](/docs/react/customize-theme-variable)。 + +## en-US + +Modify global theme color by css variable which IE not support. Css variable depends on the design, it may adjust so please do not directly use it. You can go to other components page for more detail style. [Check this](/docs/react/customize-theme-variable) to view detail. + +```jsx +import { SketchPicker } from 'react-color'; +import React, { useState } from 'react'; +import { + DownOutlined, + MailOutlined, + SettingOutlined, + ClockCircleOutlined, +} from '@ant-design/icons'; +import { + ConfigProvider, + Tag, + Mentions, + Steps, + Button, + Radio, + Space, + Form, + Input, + Row, + Col, + Typography, + Menu, + Dropdown, + Divider, + Pagination, + Select, + Checkbox, + DatePicker, + TimePicker, + InputNumber, + Slider, + Switch, + TreeSelect, + Card, + Table, + Tabs, + Timeline, + Tree, + Alert, + Progress, + Spin, + Transfer, +} from 'antd'; + +const SplitSpace = props => } size={4} {...props} />; + +const inputProps = { + style: { width: 128 }, +}; + +const selectProps = { + ...inputProps, + options: [ + { value: 'light', label: 'Light' }, + { value: 'bamboo', label: 'Bamboo' }, + { value: 'little', label: 'Little' }, + ], +}; + +const treeData = [ + { + value: 'little', + key: 'little', + label: 'Little', + title: 'Little', + children: [ + { value: 'light', key: 'light', label: 'Light', title: 'Light' }, + { value: 'bamboo', key: 'bamboo', label: 'Bamboo', title: 'Bamboo' }, + ], + }, +]; + +const treeSelectProps = { + ...inputProps, + treeCheckable: true, + maxTagCount: 'responsive', + treeData, +}; + +const carTabListNoTitle = [ + { + key: 'article', + tab: 'article', + }, + { + key: 'app', + tab: 'app', + }, + { + key: 'project', + tab: 'project', + }, +]; + +const MyTransfer = () => { + const mockData = []; + for (let i = 0; i < 20; i++) { + mockData.push({ + key: i.toString(), + title: `content${i + 1}`, + description: `description of content${i + 1}`, + }); + } + + return ( + item.title} + /> + ); +}; + +const FormSizeDemo = () => { + const [color, setColor] = useState({ + primaryColor: '#1890ff', + errorColor: '#ff4d4f', + warningColor: '#faad14', + successColor: '#52c41a', + infoColor: '#1890ff', + }); + + function onColorChange(nextColor) { + const mergedNextColor = { + ...color, + ...nextColor, + }; + setColor(mergedNextColor); + ConfigProvider.config({ + theme: mergedNextColor, + }); + } + + return ( + + + + {/* Primary Color */} + { + onColorChange({ + primaryColor: hex, + }); + }} + /> + + var(`--ant-primary-color`) + + {/* Error Color */} + { + onColorChange({ + errorColor: hex, + }); + }} + /> + + var(`--ant-error-color`) + + {/* Warning Color */} + { + onColorChange({ + warningColor: hex, + }); + }} + /> + + var(`--ant-warning-color`) + + {/* Success Color */} + { + onColorChange({ + successColor: hex, + }); + }} + /> + + var(`--ant-success-color`) + + {/* Info Color */} + { + onColorChange({ + infoColor: hex, + }); + }} + /> + + var(`--ant-info-color`) + + + + + } style={{ width: '100%' }} size={0}> + {/* Primary Button */} + + + + + + + + + {/* Danger Button */} + + + + + + + + + {/* Ghost Button */} + + + + + + + + + + {/* Typography */} + + Text (success) + Text(warning) + Text(danger) + + Link + + Text + + {/* Dropdown */} + + 1st menu item + a danger item + + } + > + e.preventDefault()}> + Hover me + + + + {/* Spin */} + + + + {/* Menu - horizontal */} + + + + }> + Mail + + } title="Submenu"> + + Option 1 + Option 2 + + + + + + + }> + Mail + + } title="Submenu"> + + Option 1 + Option 2 + + + + + + + {/* Menu - vertical */} + + + + }> + Mail + + } title="Submenu"> + + Option 1 + Option 2 + + + + + + + }> + Mail + + } title="Submenu"> + + Option 1 + Option 2 + + + + + + + {/* Pagination */} + + + {/* Steps */} + + + + + + + {/* Steps - dot */} + + + + + + + + {/* Form - Input */} +
+ + + + + + + + + + + + + + + + + +
+ + {/* Form - Select */} +
+ + + + + + + + +