import React, { useCallback, useMemo, useState } from 'react';
import DropDownButton from 'devextreme-react/drop-down-button';
import type { DropDownButtonTypes, DropDownButtonRef } from 'devextreme-react/drop-down-button';
import Toolbar from 'devextreme-react/toolbar';
import type { ToolbarTypes } from 'devextreme-react/toolbar';
import { Template } from 'devextreme-react/core/template';
import type { ButtonTypes } from 'devextreme-react/button';
import notify from 'devextreme/ui/notify';
import {
colors,
downloads,
alignments,
profileSettings,
fontSizes,
lineHeights,
} from './data.ts';
import type { TextAlign } from './types.ts';
import ColorIcon from './ColorIcon.tsx';
import DropDownButtonTemplate from './DropDownButtonTemplate.tsx';
import 'whatwg-fetch';
const buttonDropDownOptions = { width: 230 };
const itemTemplateRender: React.FC<{ size: number; text: string }> = (item) => (
<div style={{ fontSize: `${item.size}px` }}>
{item.text}
</div>
);
const App = () => {
const [alignment, setAlignment] = useState<TextAlign>('left');
const [color, setColor] = useState<string | null>(null);
const [fontSize, setFontSize] = useState<number>(14);
const [lineHeight, setLineHeight] = useState<number>(1.35);
type ColorPicker = ReturnType<DropDownButtonRef['instance']>;
const [colorPicker, setColorPicker] = useState<ColorPicker | undefined>(undefined);
const onButtonClick = useCallback((e: ButtonTypes.ClickEvent): void => {
notify(`Go to ${e.element.querySelector('.button-title').textContent}'s profile`, 'success', 600);
}, []);
const onItemClick = useCallback((e: DropDownButtonTypes.ItemClickEvent): void => {
notify(e.itemData.name || e.itemData, 'success', 600);
}, []);
const onColorClick = useCallback((selectedColor: string): void => {
setColor(selectedColor);
const squareIcon = colorPicker?.element().getElementsByClassName('dx-icon-square')[0] as HTMLElement;
squareIcon.style.color = selectedColor;
colorPicker?.close();
}, [colorPicker]);
const onInitialized = useCallback((e: DropDownButtonTypes.InitializedEvent): void => {
setColorPicker(e.component);
}, []);
const toolbarItems = useMemo((): ToolbarTypes.Item[] => [
{
location: 'before',
widget: 'dxDropDownButton',
options: {
displayExpr: 'name',
keyExpr: 'id',
selectedItemKey: 3,
width: 125,
stylingMode: 'text',
useSelectMode: true,
onSelectionChanged: (e: DropDownButtonTypes.SelectionChangedEvent): void => {
setAlignment(e.item.name.toLowerCase());
},
items: alignments,
},
},
{
location: 'before',
widget: 'dxDropDownButton',
options: {
items: colors,
icon: 'square',
stylingMode: 'text',
dropDownOptions: { width: 'auto' },
onInitialized,
dropDownContentTemplate: 'colorpicker',
},
},
{
location: 'before',
widget: 'dxDropDownButton',
options: {
stylingMode: 'text',
displayExpr: 'text',
keyExpr: 'size',
useSelectMode: true,
items: fontSizes,
selectedItemKey: 14,
onSelectionChanged: (e: DropDownButtonTypes.SelectionChangedEvent): void => {
setFontSize(e.item.size);
},
itemTemplate: 'fontItem',
},
},
{
location: 'before',
widget: 'dxDropDownButton',
options: {
stylingMode: 'text',
icon: 'indent',
displayExpr: 'text',
keyExpr: 'lineHeight',
useSelectMode: true,
items: lineHeights,
selectedItemKey: 1.35,
onSelectionChanged: (e: DropDownButtonTypes.SelectionChangedEvent): void => {
setLineHeight(e.item.lineHeight);
},
},
},
], [onInitialized]);
return (
<div>
<div className="dx-fieldset">
<div className="dx-fieldset-header">Standalone button</div>
<div className="dx-field">
<div className="dx-field-label">
Text and icon
</div>
<div className="dx-field-value">
<DropDownButton
text="Download Trial"
icon="save"
dropDownOptions={buttonDropDownOptions}
items={downloads}
onItemClick={onItemClick}
/>
</div>
</div>
<div className="dx-field">
<div className="dx-field-label">
Custom template and actions
</div>
<div className="dx-field-value">
<DropDownButton
id="custom-template"
splitButton={true}
useSelectMode={false}
items={profileSettings}
displayExpr="name"
keyExpr="id"
onButtonClick={onButtonClick}
onItemClick={onItemClick}
render={DropDownButtonTemplate}
>
</DropDownButton>
</div>
</div>
</div>
<div className="dx-fieldset">
<div className="dx-fieldset-header">Embedded in a Toolbar</div>
<div className="dx-field">
<Toolbar items={toolbarItems}>
<Template name="colorpicker">
<div className="custom-color-picker">
{colors.map((colorValue: string | null, i: number) => (
<ColorIcon key={i} color={colorValue} onClick={onColorClick} />
))}
</div>
</Template>
<Template name="fontItem" render={itemTemplateRender}>
</Template>
</Toolbar>
</div>
<div className="dx-field" style={{
color: color ?? undefined,
textAlign: alignment,
lineHeight,
fontSize: `${fontSize}px`,
}}>
<p id="text">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
</div>
</div>
);
};
export default App;
import React, { useCallback, useMemo, useState } from 'react';
import DropDownButton from 'devextreme-react/drop-down-button';
import Toolbar from 'devextreme-react/toolbar';
import { Template } from 'devextreme-react/core/template';
import notify from 'devextreme/ui/notify';
import {
colors, downloads, alignments, profileSettings, fontSizes, lineHeights,
} from './data.js';
import ColorIcon from './ColorIcon.js';
import DropDownButtonTemplate from './DropDownButtonTemplate.js';
import 'whatwg-fetch';
const buttonDropDownOptions = { width: 230 };
const itemTemplateRender = (item) => <div style={{ fontSize: `${item.size}px` }}>{item.text}</div>;
const App = () => {
const [alignment, setAlignment] = useState('left');
const [color, setColor] = useState(null);
const [fontSize, setFontSize] = useState(14);
const [lineHeight, setLineHeight] = useState(1.35);
const [colorPicker, setColorPicker] = useState(undefined);
const onButtonClick = useCallback((e) => {
notify(
`Go to ${e.element.querySelector('.button-title').textContent}'s profile`,
'success',
600,
);
}, []);
const onItemClick = useCallback((e) => {
notify(e.itemData.name || e.itemData, 'success', 600);
}, []);
const onColorClick = useCallback(
(selectedColor) => {
setColor(selectedColor);
const squareIcon = colorPicker?.element().getElementsByClassName('dx-icon-square')[0];
squareIcon.style.color = selectedColor;
colorPicker?.close();
},
[colorPicker],
);
const onInitialized = useCallback((e) => {
setColorPicker(e.component);
}, []);
const toolbarItems = useMemo(
() => [
{
location: 'before',
widget: 'dxDropDownButton',
options: {
displayExpr: 'name',
keyExpr: 'id',
selectedItemKey: 3,
width: 125,
stylingMode: 'text',
useSelectMode: true,
onSelectionChanged: (e) => {
setAlignment(e.item.name.toLowerCase());
},
items: alignments,
},
},
{
location: 'before',
widget: 'dxDropDownButton',
options: {
items: colors,
icon: 'square',
stylingMode: 'text',
dropDownOptions: { width: 'auto' },
onInitialized,
dropDownContentTemplate: 'colorpicker',
},
},
{
location: 'before',
widget: 'dxDropDownButton',
options: {
stylingMode: 'text',
displayExpr: 'text',
keyExpr: 'size',
useSelectMode: true,
items: fontSizes,
selectedItemKey: 14,
onSelectionChanged: (e) => {
setFontSize(e.item.size);
},
itemTemplate: 'fontItem',
},
},
{
location: 'before',
widget: 'dxDropDownButton',
options: {
stylingMode: 'text',
icon: 'indent',
displayExpr: 'text',
keyExpr: 'lineHeight',
useSelectMode: true,
items: lineHeights,
selectedItemKey: 1.35,
onSelectionChanged: (e) => {
setLineHeight(e.item.lineHeight);
},
},
},
],
[onInitialized],
);
return (
<div>
<div className="dx-fieldset">
<div className="dx-fieldset-header">Standalone button</div>
<div className="dx-field">
<div className="dx-field-label">Text and icon</div>
<div className="dx-field-value">
<DropDownButton
text="Download Trial"
icon="save"
dropDownOptions={buttonDropDownOptions}
items={downloads}
onItemClick={onItemClick}
/>
</div>
</div>
<div className="dx-field">
<div className="dx-field-label">Custom template and actions</div>
<div className="dx-field-value">
<DropDownButton
id="custom-template"
splitButton={true}
useSelectMode={false}
items={profileSettings}
displayExpr="name"
keyExpr="id"
onButtonClick={onButtonClick}
onItemClick={onItemClick}
render={DropDownButtonTemplate}
></DropDownButton>
</div>
</div>
</div>
<div className="dx-fieldset">
<div className="dx-fieldset-header">Embedded in a Toolbar</div>
<div className="dx-field">
<Toolbar items={toolbarItems}>
<Template name="colorpicker">
<div className="custom-color-picker">
{colors.map((colorValue, i) => (
<ColorIcon
key={i}
color={colorValue}
onClick={onColorClick}
/>
))}
</div>
</Template>
<Template
name="fontItem"
render={itemTemplateRender}
></Template>
</Toolbar>
</div>
<div
className="dx-field"
style={{
color: color ?? undefined,
textAlign: alignment,
lineHeight,
fontSize: `${fontSize}px`,
}}
>
<p id="text">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
</p>
</div>
</div>
</div>
);
};
export default App;
import React from 'react';
interface ColorIconProps {
color: string;
onClick: (color: string) => void;
}
const ColorIcon = ({ color, onClick }: ColorIconProps) => (
<i
onClick={() => onClick(color)}
className="color dx-icon dx-icon-square"
style={{ color }}
/>
);
export default ColorIcon;
import React from 'react';
interface AvatarProps {
icon: string;
}
const Avatar = ({ icon }: AvatarProps) => (
<div className="button-img-container">
<div className="button-img-indicator"></div>
<img className="button-img" src={icon} alt="employee" />
</div>
);
interface ButtonDescriptionProps {
text: string;
description: string;
}
const ButtonDescription = ({ text, description }: ButtonDescriptionProps) => (
<div className="text-container">
<div className="button-title">{text}</div>
<div className="button-row">{description}</div>
</div>
);
const DropDownButtonTemplate = () => (
<>
<Avatar icon="../../../../images/employees/51.png" />
<ButtonDescription text="Olivia Peyton" description="IT Manager" />
</>
);
export default DropDownButtonTemplate;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.tsx';
ReactDOM.render(
<App />,
document.getElementById('app'),
);
import type { ProfileSetting, Alignment, FontSize, LineHeight } from './types.ts';
export const colors: (string | null)[] = [null, '#980000', '#ff0000', '#ff9900', '#ffff00', '#00ff00', '#00ffff', '#4a86e8', '#0000ff', '#9900ff', '#ff00ff', '#ff3466'];
export const profileSettings: ProfileSetting[] = [
{ id: 1, name: 'Profile', icon: 'user' },
{ id: 4, name: 'Messages', icon: 'email', badge: '5' },
{ id: 2, name: 'Friends', icon: 'group' },
{ id: 3, name: 'Exit', icon: 'runner' },
];
export const downloads: string[] = ['Download Trial For Visual Studio', 'Download Trial For All Platforms', 'Package Managers'];
export const alignments: Alignment[] = [
{ id: 1, name: 'Left', icon: 'alignleft' },
{ id: 4, name: 'Right', icon: 'alignright' },
{ id: 2, name: 'Center', icon: 'aligncenter' },
{ id: 3, name: 'Justify', icon: 'alignjustify' },
];
export const fontSizes: FontSize[] = [
{ size: 10, text: '10px' },
{ size: 12, text: '12px' },
{ size: 14, text: '14px' },
{ size: 16, text: '16px' },
{ size: 18, text: '18px' },
];
export const lineHeights: LineHeight[] = [
{ lineHeight: 1, text: '1' },
{ lineHeight: 1.35, text: '1.35' },
{ lineHeight: 1.5, text: '1.5' },
{ lineHeight: 2, text: '2' },
];
export type ProfileSetting = {
id: number;
name: string;
icon: string;
badge?: string;
};
export type Alignment = {
id: number;
name: string;
icon: string;
};
export type FontSize = {
size: number;
text: string;
};
export type LineHeight = {
lineHeight: number;
text: string;
};
export type TextAlign = 'center' | 'end' | 'justify' | 'left' | 'match-parent' | 'right' | 'start';
window.exports = window.exports || {};
window.config = {
transpiler: 'ts',
typescriptOptions: {
module: 'system',
emitDecoratorMetadata: true,
experimentalDecorators: true,
jsx: 'react',
},
meta: {
'react': {
'esModule': true,
},
'typescript': {
'exports': 'ts',
},
'devextreme/time_zone_utils.js': {
'esModule': true,
},
'devextreme/localization.js': {
'esModule': true,
},
'devextreme/viz/palette.js': {
'esModule': true,
},
'openai': {
'esModule': true,
},
},
paths: {
'npm:': 'https://cdn.jsdelivr.net/npm/',
'bundles:': '../../../../bundles/',
'externals:': '../../../../bundles/externals/',
},
defaultExtension: 'js',
map: {
'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js',
'typescript': 'npm:typescript@4.2.4/lib/typescript.js',
'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js',
'react': 'npm:react@17.0.2/umd/react.development.js',
'react-dom': 'npm:react-dom@17.0.2/umd/react-dom.development.js',
'prop-types': 'npm:prop-types/prop-types.js',
'whatwg-fetch': 'npm:whatwg-fetch@2.0.4/fetch.js',
'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js',
'luxon': 'npm:luxon@3.4.4/build/global/luxon.min.js',
'es6-object-assign': 'npm:es6-object-assign',
'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs',
'devextreme-react': 'npm:devextreme-react@link:../../packages/devextreme-react/npm/cjs',
'devextreme-quill': 'npm:devextreme-quill@1.7.7/dist/dx-quill.min.js',
'devexpress-diagram': 'npm:devexpress-diagram@2.2.24/dist/dx-diagram.js',
'devexpress-gantt': 'npm:devexpress-gantt@4.1.64/dist/dx-gantt.js',
'inferno': 'npm:inferno@8.2.3/dist/inferno.min.js',
'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js',
'inferno-create-element': 'npm:inferno-create-element@8.2.3/dist/inferno-create-element.min.js',
'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js',
'inferno-hydrate': 'npm:inferno-hydrate/dist/inferno-hydrate.min.js',
'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js',
'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js',
'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js',
'@preact/signals-core': 'npm:@preact/signals-core@1.8.0/dist/signals-core.min.js',
'devextreme-cldr-data': 'npm:devextreme-cldr-data@1.0.3',
// SystemJS plugins
'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js',
'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js',
// Prettier
'prettier/standalone': 'npm:prettier@2.8.8/standalone.js',
'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js',
},
packages: {
'devextreme': {
defaultExtension: 'js',
},
'devextreme-react': {
main: 'index.js',
},
'devextreme-react/common': {
main: 'index.js',
},
'devextreme/events/utils': {
main: 'index',
},
'devextreme/common/core/events/utils': {
main: 'index',
},
'devextreme/localization/messages': {
format: 'json',
defaultExtension: 'json',
},
'devextreme/events': {
main: 'index',
},
'es6-object-assign': {
main: './index.js',
defaultExtension: 'js',
},
},
packageConfigPaths: [
'npm:@devextreme/*/package.json',
],
babelOptions: {
sourceMaps: false,
stage0: true,
react: true,
},
};
window.process = {
env: {
NODE_ENV: 'production',
},
};
System.config(window.config);
// eslint-disable-next-line
const useTgzInCSB = ['openai'];
import React from 'react';
const ColorIcon = ({ color, onClick }) => (
<i
onClick={() => onClick(color)}
className="color dx-icon dx-icon-square"
style={{ color }}
/>
);
export default ColorIcon;
import React from 'react';
const Avatar = ({ icon }) => (
<div className="button-img-container">
<div className="button-img-indicator"></div>
<img
className="button-img"
src={icon}
alt="employee"
/>
</div>
);
const ButtonDescription = ({ text, description }) => (
<div className="text-container">
<div className="button-title">{text}</div>
<div className="button-row">{description}</div>
</div>
);
const DropDownButtonTemplate = () => (
<>
<Avatar icon="../../../../images/employees/51.png" />
<ButtonDescription
text="Olivia Peyton"
description="IT Manager"
/>
</>
);
export default DropDownButtonTemplate;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
ReactDOM.render(<App />, document.getElementById('app'));
export const colors = [
null,
'#980000',
'#ff0000',
'#ff9900',
'#ffff00',
'#00ff00',
'#00ffff',
'#4a86e8',
'#0000ff',
'#9900ff',
'#ff00ff',
'#ff3466',
];
export const profileSettings = [
{ id: 1, name: 'Profile', icon: 'user' },
{
id: 4, name: 'Messages', icon: 'email', badge: '5',
},
{ id: 2, name: 'Friends', icon: 'group' },
{ id: 3, name: 'Exit', icon: 'runner' },
];
export const downloads = [
'Download Trial For Visual Studio',
'Download Trial For All Platforms',
'Package Managers',
];
export const alignments = [
{ id: 1, name: 'Left', icon: 'alignleft' },
{ id: 4, name: 'Right', icon: 'alignright' },
{ id: 2, name: 'Center', icon: 'aligncenter' },
{ id: 3, name: 'Justify', icon: 'alignjustify' },
];
export const fontSizes = [
{ size: 10, text: '10px' },
{ size: 12, text: '12px' },
{ size: 14, text: '14px' },
{ size: 16, text: '16px' },
{ size: 18, text: '18px' },
];
export const lineHeights = [
{ lineHeight: 1, text: '1' },
{ lineHeight: 1.35, text: '1.35' },
{ lineHeight: 1.5, text: '1.5' },
{ lineHeight: 2, text: '2' },
];
<!DOCTYPE html>
<html lang="en">
<head>
<title>DevExtreme Demo</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
<link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/25.2.3/css/dx.light.css" />
<link rel="stylesheet" type="text/css" href="styles.css" />
<script src="https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.3/dist/system.js"></script>
<script type="text/javascript" src="config.js"></script>
<script type="text/javascript">
System.import("./index.tsx");
</script>
</head>
<body class="dx-viewport">
<div class="demo-container">
<div id="app"></div>
</div>
</body>
</html>
.demo-container .dx-fieldset:first-child {
width: 500px;
}
.custom-color-picker {
width: 82px;
padding: 5px;
}
.color {
cursor: pointer;
font-size: 18px;
}
.text-container {
padding-inline: 12px 4px;
display: flex;
align-items: flex-start;
flex-direction: column;
}
.button-img-container {
position: relative;
height: 32px;
}
.button-img {
width: 32px;
height: 32px;
position: relative;
border-width: 1px;
border-style: solid;
border-color: var(--dx-color-border);
border-radius: 50%;
}
.button-img-indicator {
position: absolute;
background-color: var(--dx-color-danger);
top: -1px;
inset-inline-end: -1px;
width: 10px;
height: 10px;
border-radius: 50%;
border-width: 2px;
border-style: solid;
border-color: var(--dx-color-main-bg);
z-index: 1;
}
.button-title {
line-height: 20px;
}
.button-row {
font-size: 12px;
line-height: 14px;
opacity: 0.6;
}
#custom-template .dx-button {
height: 46px;
}
#custom-template .dx-button.dx-dropdownbutton-action .dx-button-content {
padding-inline: 12px 12px;
}