Skip to content

Commit 098ea68

Browse files
authored
Merge pull request #3536 from plotly/feature/dcc-button
Feature: `dcc.Button`
2 parents cc21d09 + 42a76df commit 098ea68

File tree

6 files changed

+445
-1
lines changed

6 files changed

+445
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
88
- Modernized `dcc.Tabs`
99
- Modernized `dcc.DatePickerSingle` and `dcc.DatePickerRange`
1010
- DatePicker calendars can now accept translations as an external script, either with Dash's `external_scripts` or from the assets folder. See [documentation](https://date-fns.org/v4.1.0/docs/CDN) for the underlying library that supports this.
11+
- New `dcc.Button` component that mirrors `html.Button` but with default styles applied
1112

1213
## Changed
1314
- `dcc.Tab` now accepts a `width` prop which can be a pixel or percentage width for an individual tab.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import {ButtonProps} from '../types';
3+
import './css/button.css';
4+
5+
/**
6+
* Similar to dash.html.Button, but with theming and styles applied.
7+
*/
8+
const Button = ({
9+
setProps,
10+
n_blur = 0,
11+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
12+
n_blur_timestamp = -1,
13+
n_clicks = 0,
14+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
15+
n_clicks_timestamp = -1,
16+
type = 'button',
17+
className,
18+
children,
19+
...props
20+
}: ButtonProps) => {
21+
const ctx = window.dash_component_api.useDashContext();
22+
const isLoading = ctx.useLoading();
23+
24+
return (
25+
<button
26+
data-dash-is-loading={isLoading || undefined}
27+
className={'dash-button ' + (className ?? '')}
28+
onBlur={() => {
29+
setProps({
30+
n_blur: n_blur + 1,
31+
n_blur_timestamp: Date.now(),
32+
});
33+
}}
34+
onClick={() => {
35+
setProps({
36+
n_clicks: n_clicks + 1,
37+
n_clicks_timestamp: Date.now(),
38+
});
39+
}}
40+
type={type}
41+
{...props}
42+
>
43+
{children}
44+
</button>
45+
);
46+
};
47+
48+
export default Button;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
.dash-button {
2+
line-height: 32px;
3+
background: color-mix(
4+
in srgb,
5+
var(--Dash-Fill-Interactive-Strong) 5%,
6+
transparent
7+
);
8+
color: var(--Dash-Fill-Interactive-Strong);
9+
padding: 0 calc(var(--Dash-Spacing) * 2);
10+
border-radius: 4px;
11+
border: 1px solid var(--Dash-Fill-Interactive-Strong);
12+
box-sizing: border-box;
13+
vertical-align: middle;
14+
}
15+
16+
/* Hover state - stronger background */
17+
.dash-button:hover {
18+
cursor: pointer;
19+
background: var(--Dash-Fill-Inverse-Strong);
20+
color: var(--Dash-Fill-Interactive-Strong);
21+
}
22+
23+
/* Active state (clicking) - inverted colors */
24+
.dash-button:active {
25+
background: var(--Dash-Fill-Interactive-Strong);
26+
color: var(--Dash-Fill-Inverse-Strong);
27+
}
28+
29+
/* Keyboard focus - inverted colors */
30+
.dash-button:focus-visible {
31+
outline: none;
32+
background: var(--Dash-Fill-Interactive-Strong);
33+
color: var(--Dash-Fill-Inverse-Strong);
34+
}
35+
36+
/* Hover after keyboard focus - keep inverted but acknowledge hover */
37+
.dash-button:focus-visible:hover {
38+
background: var(--Dash-Fill-Interactive-Strong);
39+
color: var(--Dash-Fill-Inverse-Strong);
40+
}
41+
42+
/* Active state after keyboard focus - inverted colors */
43+
.dash-button:focus-visible:active {
44+
background: var(--Dash-Fill-Interactive-Strong);
45+
color: var(--Dash-Fill-Inverse-Strong);
46+
}
47+
48+
/* Disabled state */
49+
.dash-button:disabled {
50+
opacity: 0.6;
51+
cursor: not-allowed;
52+
background: var(--Dash-Fill-Disabled);
53+
color: var(--Dash-Text-Strong);
54+
border-color: var(--Dash-Stroke-Weak);
55+
}

components/dash-core-components/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* eslint-disable import/prefer-default-export */
2+
import Button from './components/Button';
23
import Checklist from './components/Checklist';
34
import Clipboard from './components/Clipboard.react';
45
import ConfirmDialog from './components/ConfirmDialog.react';
@@ -28,6 +29,7 @@ import Upload from './components/Upload.react';
2829
import './components/css/dcc.css';
2930

3031
export {
32+
Button,
3133
Checklist,
3234
Clipboard,
3335
ConfirmDialog,

components/dash-core-components/src/types.ts

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, {ButtonHTMLAttributes, DetailedHTMLProps} from 'react';
22
import {BaseDashProps, DashComponent} from '@dash-renderer/types';
33

44
export enum PersistenceTypes {
@@ -52,6 +52,117 @@ export interface BaseDccProps<T>
5252
persistence_type?: PersistenceTypes;
5353
}
5454

55+
export type ButtonProps = BaseDccProps<ButtonProps> & {
56+
/**
57+
* The children of this component.
58+
*/
59+
children?: React.ReactNode;
60+
/**
61+
* Defines the type of the element.
62+
*/
63+
type?: 'submit' | 'reset' | 'button';
64+
/**
65+
* The element should be automatically focused after the page loaded.
66+
*/
67+
autoFocus?: boolean;
68+
/**
69+
* Indicates whether the user can interact with the element.
70+
*/
71+
disabled?: boolean;
72+
/**
73+
* Indicates the form that is the owner of the element.
74+
*/
75+
form?: string;
76+
/**
77+
* Indicates the action of the element, overriding the action defined in the <form>.
78+
*/
79+
formAction?: string;
80+
/**
81+
* If the button/input is a submit button (type="submit"), this attribute sets the encoding type to use during form submission. If this attribute is specified, it overrides the enctype attribute of the button's form owner.
82+
*/
83+
formEncType?: string;
84+
/**
85+
* If the button/input is a submit button (type="submit"), this attribute sets the submission method to use during form submission (GET, POST, etc.). If this attribute is specified, it overrides the method attribute of the button's form owner.
86+
*/
87+
formMethod?: string;
88+
/**
89+
* If the button/input is a submit button (type="submit"), this boolean attribute specifies that the form is not to be validated when it is submitted. If this attribute is specified, it overrides the novalidate attribute of the button's form owner.
90+
*/
91+
formNoValidate?: boolean;
92+
/**
93+
* If the button/input is a submit button (type="submit"), this attribute specifies the browsing context (for example, tab, window, or inline frame) in which to display the response that is received after submitting the form. If this attribute is specified, it overrides the target attribute of the button's form owner.
94+
*/
95+
formTarget?: string;
96+
/**
97+
* Name of the element. For example used by the server to identify the fields in form submits.
98+
*/
99+
name?: string;
100+
/**
101+
* Defines a default value which will be displayed in the element on page load.
102+
*/
103+
value?: string | string[] | number;
104+
/**
105+
* Keyboard shortcut to activate or add focus to the element.
106+
*/
107+
accessKey?: string;
108+
/**
109+
* Indicates whether the element's content is editable.
110+
*/
111+
contentEditable?: boolean | 'true' | 'false' | 'inherit';
112+
/**
113+
* Defines the text direction. Allowed values are ltr (Left-To-Right) or rtl (Right-To-Left).
114+
*/
115+
dir?: string;
116+
/**
117+
* Defines whether the element can be dragged.
118+
*/
119+
draggable?: boolean;
120+
/**
121+
* Prevents rendering of given element, while keeping child elements, e.g. script elements, active.
122+
*/
123+
hidden?: boolean;
124+
/**
125+
* Defines the language used in the element.
126+
*/
127+
lang?: string;
128+
/**
129+
* Defines the role of an element in the context of accessibility.
130+
*/
131+
role?: string;
132+
/**
133+
* Indicates whether spell checking is allowed for the element.
134+
*/
135+
spellCheck?: boolean;
136+
/**
137+
* Defines CSS styles which will override styles previously set.
138+
*/
139+
style?: React.CSSProperties;
140+
/**
141+
* Overrides the browser's default tab order and follows the one specified instead.
142+
*/
143+
tabIndex?: number;
144+
/**
145+
* Text to be displayed in a tooltip when hovering over the element.
146+
*/
147+
title?: string;
148+
/**
149+
* Number of times the button lost focus.
150+
*/
151+
n_blur?: number;
152+
/**
153+
* Last time the button lost focus.
154+
*/
155+
n_blur_timestamp?: number;
156+
/**
157+
* Number of times the button has been clicked.
158+
*/
159+
n_clicks?: number;
160+
/**
161+
* Last time the button was clicked.
162+
*/
163+
n_clicks_timestamp?: number;
164+
};
165+
55166
export enum HTMLInputTypes {
56167
// Only allowing the input types with wide browser compatibility
57168
'text' = 'text',

0 commit comments

Comments
 (0)