Skip to content

Commit 394b16b

Browse files
authored
Button: Adding back block, circular, disabledFocusable and outline props and making some styling fixes (#18563)
* Clean git history. * Button: Adding back block prop. * Change files * Change files * Removing unused change files. * Button: Adding disabledFocusable and outline props back to button. * Adding vr tests. * Change files * Using theme value for circular styling. * Adding checkedOutline styles to ToggleButton. * Removing unwanted onClick callback in Button stories. * Addressing PR feedback. * Reverting prettier update. * Reverting prettier update. * Reverting prettier update.
1 parent 7de2dec commit 394b16b

File tree

13 files changed

+508
-150
lines changed

13 files changed

+508
-150
lines changed

apps/vr-tests/src/stories/ReactButton.stories.tsx

Lines changed: 349 additions & 96 deletions
Large diffs are not rendered by default.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "none",
3+
"comment": "Prettier update.",
4+
"packageName": "@fluentui/react",
5+
"email": "Humberto.Morimoto@microsoft.com",
6+
"dependentChangeType": "none"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Button: Adding back block, circular, disabledFocusable and outline props and making some styling fixes.",
4+
"packageName": "@fluentui/react-button",
5+
"email": "Humberto.Morimoto@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Adding block, circular, disabledFocusable and outline props to Button stories.",
4+
"packageName": "@fluentui/react-examples",
5+
"email": "Humberto.Morimoto@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Link: Adding missing state management to handle disabledFocusable scenarios.",
4+
"packageName": "@fluentui/react-link",
5+
"email": "Humberto.Morimoto@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

packages/react-button/etc/react-button.api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ export type ButtonDefaultedProps = 'icon' | 'size';
1919
export type ButtonProps = ComponentPropsCompat & React_2.ButtonHTMLAttributes<HTMLElement> & {
2020
children?: ShorthandPropsCompat<React_2.HTMLAttributes<HTMLElement>>;
2121
icon?: ShorthandPropsCompat<React_2.HTMLAttributes<HTMLElement>>;
22+
block?: boolean;
23+
circular?: boolean;
2224
disabled?: boolean;
25+
disabledFocusable?: boolean;
2326
iconPosition?: 'before' | 'after';
27+
outline?: boolean;
2428
primary?: boolean;
2529
size?: 'small' | 'medium' | 'large';
2630
subtle?: boolean;

packages/react-button/src/components/Button/Button.types.ts

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,31 @@ export type ButtonProps = ComponentPropsCompat &
2121
// */
2222
// loader?: ShorthandPropsCompat<React.HTMLAttributes<HTMLSpanElement>>;
2323

24-
// /**
25-
// * A button can fill the width of its container.
26-
// * @default false
27-
// */
28-
// block?: boolean;
24+
/**
25+
* A button can fill the width of its container.
26+
* @default false
27+
*/
28+
block?: boolean;
2929

30-
// /**
31-
// * A button can have completely rounded corners.
32-
// * @default false
33-
// */
34-
// circular?: boolean;
30+
/**
31+
* A button can have completely rounded corners.
32+
* @default false
33+
*/
34+
circular?: boolean;
3535

3636
/**
3737
* A button can show that it cannot be interacted with.
3838
* @default false
3939
*/
4040
disabled?: boolean;
4141

42-
// /**
43-
// * When set, allows the button to be focusable even when it has been disabled. This is used in scenarios where it
44-
// * is important to keep a consistent tab order for screen reader and keyboard users.
45-
// * @default false
46-
// */
47-
// disabledFocusable?: boolean;
42+
/**
43+
* When set, allows the button to be focusable even when it has been disabled. This is used in scenarios where it
44+
* is important to keep a consistent tab order for screen reader and keyboard users. The primary example of this
45+
* pattern is when the disabled button is in a menu or a commandbar and is seldom used for standalone buttons.
46+
* @default false
47+
*/
48+
disabledFocusable?: boolean;
4849

4950
/**
5051
* A button can format its icon to appear before or after its content.
@@ -59,13 +60,13 @@ export type ButtonProps = ComponentPropsCompat &
5960
// */
6061
// loading?: boolean;
6162

62-
// /**
63-
// * A button can be styled such that it has no background styling and is just emphasized through the styling of
64-
// * its content and borders.
65-
// * Mutually exclusive with `primary`, `subtle` and `transparent`.
66-
// * @default false
67-
// */
68-
// outline?: boolean;
63+
/**
64+
* A button can be styled such that it has no background styling and is just emphasized through the styling of
65+
* its content and borders.
66+
* Mutually exclusive with `primary`, `subtle` and `transparent`.
67+
* @default false
68+
*/
69+
outline?: boolean;
6970

7071
/**
7172
* A button can be styled to emphasize that it represents the primary action.

packages/react-button/src/components/Button/useButtonState.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ButtonState } from './Button.types';
77
* @param state - Button draft state to mutate.
88
*/
99
export const useButtonState = (state: ButtonState): ButtonState => {
10-
const { as, children, disabled, icon, onClick, onKeyDown: onKeyDownCallback } = state;
10+
const { as, children, disabled, disabledFocusable, icon, onClick, onKeyDown: onKeyDownCallback } = state;
1111

1212
const receivedChildren = !!children?.children;
1313
const receivedIcon = !!icon?.children;
@@ -31,7 +31,7 @@ export const useButtonState = (state: ButtonState): ButtonState => {
3131
// Add 'role=button' and 'tabIndex=0' for all non-button elements.
3232
if (as !== 'button') {
3333
state.role = 'button';
34-
state.tabIndex = disabled /*&& !disabledFocusable*/ ? undefined : 0;
34+
state.tabIndex = disabled && !disabledFocusable ? undefined : 0;
3535

3636
// Add keydown event handler for all other non-anchor elements.
3737
if (as !== 'a') {
@@ -43,12 +43,12 @@ export const useButtonState = (state: ButtonState): ButtonState => {
4343
else {
4444
state.onKeyDown = onNonAnchorOrButtonKeyDown;
4545
state.role = 'button';
46-
state.tabIndex = disabled /*&& !disabledFocusable*/ ? undefined : 0;
46+
state.tabIndex = disabled && !disabledFocusable ? undefined : 0;
4747
}
4848

4949
// Disallow click event when component is disabled and eat events when disabledFocusable is set to true.
5050
state.onClick = (ev: React.MouseEvent<HTMLElement>) => {
51-
if (disabled) {
51+
if (disabled || disabledFocusable) {
5252
ev.preventDefault();
5353
} else {
5454
onClick?.(ev);
@@ -59,7 +59,7 @@ export const useButtonState = (state: ButtonState): ButtonState => {
5959
const { onKeyDown } = state;
6060
state.onKeyDown = (ev: React.KeyboardEvent<HTMLElement>) => {
6161
const keyCode = getCode(ev);
62-
if (disabled && (keyCode === EnterKey || keyCode === SpacebarKey)) {
62+
if ((disabled || disabledFocusable) && (keyCode === EnterKey || keyCode === SpacebarKey)) {
6363
ev.preventDefault();
6464
ev.stopPropagation();
6565
} else {
@@ -68,8 +68,8 @@ export const useButtonState = (state: ButtonState): ButtonState => {
6868
};
6969

7070
// Set the aria-disabled and disabled props correctly.
71-
state['aria-disabled'] = disabled /*|| disabledFocusable*/;
72-
state.disabled = as === 'button' ? disabled /* && !disabledFocusable*/ : undefined;
71+
state.disabled = as === 'button' ? disabled && !disabledFocusable : undefined;
72+
state['aria-disabled'] = disabled && !state.disabled;
7373

7474
return state;
7575
};

packages/react-button/src/components/Button/useButtonStyles.ts

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mergeClasses, makeStyles } from '@fluentui/react-make-styles';
1+
import { makeStyles, mergeClasses } from '@fluentui/react-make-styles';
22
import { createFocusIndicatorStyleRule } from '@fluentui/react-tabster';
33
import { ButtonState } from './Button.types';
44

@@ -52,10 +52,6 @@ const useRootStyles = makeStyles({
5252
outline: 'none',
5353
},
5454
}),
55-
focusIndicator: createFocusIndicatorStyleRule(theme => ({
56-
border: `2px solid ${theme.alias.color.neutral.neutralForeground1}`,
57-
borderRadius: '4px',
58-
})),
5955
small: theme => ({
6056
// TODO: remove unsafe property: https://caniuse.com/?search=gap
6157
gap: buttonSpacing.smaller,
@@ -86,6 +82,24 @@ const useRootStyles = makeStyles({
8682

8783
borderRadius: theme.global.borderRadius.medium,
8884
}),
85+
block: {
86+
maxWidth: '100%',
87+
width: '100%',
88+
},
89+
circular: theme => ({
90+
borderRadius: theme.global.borderRadius.circular,
91+
}),
92+
outline: theme => ({
93+
background: theme.alias.color.neutral.transparentBackground,
94+
95+
':hover': {
96+
background: theme.alias.color.neutral.transparentBackgroundHover,
97+
},
98+
99+
':active': {
100+
background: theme.alias.color.neutral.transparentBackgroundPressed,
101+
},
102+
}),
89103
primary: theme => ({
90104
background: theme.alias.color.neutral.brandBackground,
91105
borderColor: 'transparent',
@@ -162,14 +176,16 @@ const useRootStyles = makeStyles({
162176

163177
boxShadow: 'none',
164178

165-
cursor: 'default',
179+
cursor: 'not-allowed',
166180

167181
':hover': {
168182
background: theme.alias.color.neutral.neutralBackgroundDisabled,
169183
borderColor: theme.alias.color.neutral.neutralStrokeDisabled,
170184
color: theme.alias.color.neutral.neutralForegroundDisabled,
171185

172186
boxShadow: 'none',
187+
188+
cursor: 'not-allowed',
173189
},
174190

175191
':active': {
@@ -178,6 +194,19 @@ const useRootStyles = makeStyles({
178194
color: theme.alias.color.neutral.neutralForegroundDisabled,
179195

180196
boxShadow: 'none',
197+
198+
cursor: 'not-allowed',
199+
},
200+
}),
201+
disabledOutline: theme => ({
202+
background: theme.alias.color.neutral.transparentBackground,
203+
204+
':hover': {
205+
background: theme.alias.color.neutral.transparentBackgroundHover,
206+
},
207+
208+
':active': {
209+
background: theme.alias.color.neutral.transparentBackgroundPressed,
181210
},
182211
}),
183212
disabledPrimary: {
@@ -221,6 +250,20 @@ const useRootStyles = makeStyles({
221250
},
222251
});
223252

253+
const useRootFocusStyles = makeStyles({
254+
base: createFocusIndicatorStyleRule(theme => ({
255+
border: `2px solid ${theme.alias.color.neutral.neutralForeground1}`,
256+
borderRadius: '4px',
257+
})),
258+
circular: createFocusIndicatorStyleRule(theme => ({
259+
borderRadius: theme.global.borderRadius.circular,
260+
})),
261+
primary: createFocusIndicatorStyleRule(theme => ({
262+
border: `1px solid ${theme.alias.color.neutral.neutralForegroundInvertedAccessible}`,
263+
boxShadow: `${theme.alias.shadow.shadow2}, 0 0 0 2px ${theme.alias.color.neutral.neutralForeground1}`,
264+
})),
265+
});
266+
224267
const useRootIconOnlyStyles = makeStyles({
225268
small: {
226269
padding: buttonSpacing.smaller,
@@ -243,11 +286,11 @@ const useRootIconOnlyStyles = makeStyles({
243286
});
244287

245288
const useChildrenStyles = makeStyles({
246-
base: theme => ({
289+
base: {
247290
textOverflow: 'ellipsis',
248291
overflow: 'hidden',
249292
whiteSpace: 'nowrap',
250-
}),
293+
},
251294
small: theme => ({
252295
fontSize: theme.global.type.fontSizes.base[200],
253296
fontWeight: theme.global.type.fontWeights.regular,
@@ -290,21 +333,28 @@ const useIconStyles = makeStyles({
290333

291334
export const useButtonStyles = (state: ButtonState): ButtonState => {
292335
const rootStyles = useRootStyles();
336+
const rootFocusStyles = useRootFocusStyles();
293337
const rootIconOnlyStyles = useRootIconOnlyStyles();
294338
const childrenStyles = useChildrenStyles();
295339
const iconStyles = useIconStyles();
296340

297341
state.className = mergeClasses(
298342
rootStyles.base,
299-
rootStyles.focusIndicator,
343+
rootFocusStyles.base,
300344
rootStyles[state.size],
345+
state.block && rootStyles.block,
346+
state.circular && rootStyles.circular,
347+
state.circular && rootFocusStyles.circular,
348+
state.outline && rootStyles.outline,
301349
state.primary && rootStyles.primary,
350+
state.primary && rootFocusStyles.primary,
302351
state.subtle && rootStyles.subtle,
303352
state.transparent && rootStyles.transparent,
304-
state.disabled && rootStyles.disabled,
305-
state.disabled && state.primary && rootStyles.disabledPrimary,
306-
state.disabled && state.subtle && rootStyles.disabledSubtle,
307-
state.disabled && state.transparent && rootStyles.disabledTransparent,
353+
(state.disabled || state.disabledFocusable) && rootStyles.disabled,
354+
(state.disabled || state.disabledFocusable) && state.outline && rootStyles.disabledOutline,
355+
(state.disabled || state.disabledFocusable) && state.primary && rootStyles.disabledPrimary,
356+
(state.disabled || state.disabledFocusable) && state.subtle && rootStyles.disabledSubtle,
357+
(state.disabled || state.disabledFocusable) && state.transparent && rootStyles.disabledTransparent,
308358
state.iconOnly && rootIconOnlyStyles[state.size],
309359
state.className,
310360
);

packages/react-button/src/components/CompoundButton/useCompoundButtonStyles.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,20 @@ const useRootStyles = makeStyles({
110110

111111
const useRootIconOnlyStyles = makeStyles({
112112
small: {
113+
padding: buttonSpacing.smaller,
114+
113115
maxWidth: '48px',
114116
minWidth: '48px',
115117
},
116118
medium: {
119+
padding: buttonSpacing.small,
120+
117121
maxWidth: '52px',
118122
minWidth: '52px',
119123
},
120124
large: {
125+
padding: buttonSpacing.medium,
126+
121127
maxWidth: '56px',
122128
minWidth: '56px',
123129
},
@@ -186,7 +192,7 @@ export const useCompoundButtonStyles = (state: CompoundButtonState): CompoundBut
186192
state.primary && rootStyles.primary,
187193
state.subtle && rootStyles.subtle,
188194
state.transparent && rootStyles.transparent,
189-
state.disabled && rootStyles.disabled,
195+
(state.disabled || state.disabledFocusable) && rootStyles.disabled,
190196
state.iconOnly && rootIconOnlyStyles[state.size],
191197
state.className,
192198
);

0 commit comments

Comments
 (0)