Skip to content

Commit b1e9bba

Browse files
feat: Add native attributes for button dropdown (#3700)
1 parent a094ce2 commit b1e9bba

File tree

5 files changed

+165
-4
lines changed

5 files changed

+165
-4
lines changed

src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5680,6 +5680,77 @@ The main action also supports the following properties of the [button](/componen
56805680
"optional": true,
56815681
"type": "ButtonDropdownProps.MainAction",
56825682
},
5683+
{
5684+
"description": "Attributes to add to the native element of the \`mainAction\`.
5685+
5686+
Specify either \`button\` for a standard main action, or \`anchor\` if \`html\` is set.
5687+
5688+
Some attributes will be automatically combined with internal attribute values:
5689+
- \`className\` will be appended.
5690+
- Event handlers will be chained, unless the default is prevented.
5691+
5692+
We do not support using this attribute to apply custom styling.",
5693+
"inlineType": {
5694+
"name": "object",
5695+
"properties": [
5696+
{
5697+
"inlineType": {
5698+
"name": "Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "children"> & Record<\`data-\${string}\`, string>",
5699+
"type": "union",
5700+
"values": [
5701+
"Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "children">",
5702+
"Record<\`data-\${string}\`, string>",
5703+
],
5704+
},
5705+
"name": "anchor",
5706+
"optional": true,
5707+
"type": "Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "children"> & Record<\`data-\${string}\`, string>",
5708+
},
5709+
{
5710+
"inlineType": {
5711+
"name": "Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> & Record<\`data-\${string}\`, string>",
5712+
"type": "union",
5713+
"values": [
5714+
"Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children">",
5715+
"Record<\`data-\${string}\`, string>",
5716+
],
5717+
},
5718+
"name": "button",
5719+
"optional": true,
5720+
"type": "Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> & Record<\`data-\${string}\`, string>",
5721+
},
5722+
],
5723+
"type": "object",
5724+
},
5725+
"name": "nativeMainActionAttributes",
5726+
"optional": true,
5727+
"systemTags": [
5728+
"core",
5729+
],
5730+
"type": "{ button?: NativeAttributes<React.ButtonHTMLAttributes<HTMLButtonElement>>; anchor?: NativeAttributes<React.AnchorHTMLAttributes<HTMLAnchorElement>>; }",
5731+
},
5732+
{
5733+
"description": "Attributes to add to the native \`button\` element.
5734+
Some attributes will be automatically combined with internal attribute values:
5735+
- \`className\` will be appended.
5736+
- Event handlers will be chained, unless the default is prevented.
5737+
5738+
We do not support using this attribute to apply custom styling.",
5739+
"inlineType": {
5740+
"name": "Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> & Record<\`data-\${string}\`, string>",
5741+
"type": "union",
5742+
"values": [
5743+
"Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children">",
5744+
"Record<\`data-\${string}\`, string>",
5745+
],
5746+
},
5747+
"name": "nativeTriggerAttributes",
5748+
"optional": true,
5749+
"systemTags": [
5750+
"core",
5751+
],
5752+
"type": "Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> & Record<\`data-\${string}\`, string>",
5753+
},
56835754
{
56845755
"defaultValue": "'normal'",
56855756
"description": "Determines the general styling of the button dropdown.

src/button-dropdown/__tests__/button-dropdown.test.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,3 +492,50 @@ test('checkbox item test util should return null if not a checkbox item', () =>
492492
// findItemValueById should return the value if item checkbox
493493
expect(wrapper.findItemCheckedById('i5')).toBe('true');
494494
});
495+
496+
describe('native attributes', () => {
497+
test('adds nativeTriggerAttributes', () => {
498+
const wrapper = renderButtonDropdown({
499+
items,
500+
nativeTriggerAttributes: {
501+
'aria-describedby': 'my-custom-description',
502+
},
503+
});
504+
expect(wrapper.findTriggerButton()?.getElement()).toHaveAttribute('aria-describedby', 'my-custom-description');
505+
});
506+
test('adds nativeTriggerAttributes but not to mainAction', () => {
507+
const wrapper = renderButtonDropdown({
508+
items,
509+
mainAction: { text: 'Main' },
510+
nativeTriggerAttributes: {
511+
'aria-describedby': 'my-custom-description',
512+
},
513+
});
514+
expect(wrapper.findTriggerButton()?.getElement()).toHaveAttribute('aria-describedby', 'my-custom-description');
515+
expect(wrapper.findMainAction()?.getElement()).not.toHaveAttribute('aria-describedby', 'my-custom-description');
516+
});
517+
test('adds nativeMainActionAttributes to button-style mainAction', () => {
518+
const wrapper = renderButtonDropdown({
519+
items,
520+
mainAction: { text: 'Main' },
521+
nativeMainActionAttributes: {
522+
button: {
523+
'aria-describedby': 'my-custom-description',
524+
},
525+
},
526+
});
527+
expect(wrapper.findMainAction()?.getElement()).toHaveAttribute('aria-describedby', 'my-custom-description');
528+
});
529+
test('adds nativeMainActionAttributes to link-style mainAction', () => {
530+
const wrapper = renderButtonDropdown({
531+
items,
532+
mainAction: { text: 'Main', href: '#' },
533+
nativeMainActionAttributes: {
534+
anchor: {
535+
'aria-describedby': 'my-custom-description',
536+
},
537+
},
538+
});
539+
expect(wrapper.findMainAction()?.getElement()).toHaveAttribute('aria-describedby', 'my-custom-description');
540+
});
541+
});

src/button-dropdown/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const ButtonDropdown = React.forwardRef(
3434
onItemFollow,
3535
mainAction,
3636
fullWidth,
37+
nativeMainActionAttributes,
38+
nativeTriggerAttributes,
3739
...props
3840
}: ButtonDropdownProps,
3941
ref: React.Ref<ButtonDropdownProps.Ref>
@@ -73,6 +75,8 @@ const ButtonDropdown = React.forwardRef(
7375
onItemFollow={onItemFollow}
7476
mainAction={mainAction}
7577
fullWidth={fullWidth}
78+
nativeMainActionAttributes={nativeMainActionAttributes}
79+
nativeTriggerAttributes={nativeTriggerAttributes}
7680
{...getAnalyticsMetadataAttribute({
7781
component: analyticsComponentMetadata,
7882
})}

src/button-dropdown/interfaces.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import { BaseComponentProps } from '../internal/base-component';
1010
import { ExpandToViewport } from '../internal/components/dropdown/interfaces';
1111
import { BaseNavigationDetail, CancelableEventHandler } from '../internal/events';
1212
import { InternalBaseComponentProps } from '../internal/hooks/use-base-component';
13+
/**
14+
* @awsuiSystem core
15+
*/
16+
import { NativeAttributes } from '../internal/utils/with-native-attributes';
1317

1418
export interface ButtonDropdownProps extends BaseComponentProps, ExpandToViewport {
1519
/**
@@ -119,6 +123,36 @@ export interface ButtonDropdownProps extends BaseComponentProps, ExpandToViewpor
119123
* Sets the button width to be 100% of the parent container width. Button content is centered.
120124
*/
121125
fullWidth?: boolean;
126+
127+
/**
128+
* Attributes to add to the native `button` element.
129+
* Some attributes will be automatically combined with internal attribute values:
130+
* - `className` will be appended.
131+
* - Event handlers will be chained, unless the default is prevented.
132+
*
133+
* We do not support using this attribute to apply custom styling.
134+
*
135+
* @awsuiSystem core
136+
*/
137+
nativeTriggerAttributes?: NativeAttributes<React.ButtonHTMLAttributes<HTMLButtonElement>>;
138+
139+
/**
140+
* Attributes to add to the native element of the `mainAction`.
141+
*
142+
* Specify either `button` for a standard main action, or `anchor` if `html` is set.
143+
*
144+
* Some attributes will be automatically combined with internal attribute values:
145+
* - `className` will be appended.
146+
* - Event handlers will be chained, unless the default is prevented.
147+
*
148+
* We do not support using this attribute to apply custom styling.
149+
*
150+
* @awsuiSystem core
151+
*/
152+
nativeMainActionAttributes?: {
153+
button?: NativeAttributes<React.ButtonHTMLAttributes<HTMLButtonElement>>;
154+
anchor?: NativeAttributes<React.AnchorHTMLAttributes<HTMLAnchorElement>>;
155+
};
122156
}
123157

124158
export namespace ButtonDropdownProps {

src/button-dropdown/internal.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ const InternalButtonDropdown = React.forwardRef(
5656
linkStyle,
5757
fullWidth,
5858
position,
59+
nativeMainActionAttributes,
60+
nativeTriggerAttributes,
5961
...props
6062
}: InternalButtonDropdownProps,
6163
ref: React.Ref<ButtonDropdownProps.Ref>
@@ -76,7 +78,7 @@ const InternalButtonDropdown = React.forwardRef(
7678
warnOnce('ButtonDropdown', 'Main action is only supported for "primary" and "normal" component variant.');
7779
}
7880
}
79-
const isMainAction = mainAction && (variant === 'primary' || variant === 'normal');
81+
const hasMainAction = mainAction && (variant === 'primary' || variant === 'normal');
8082
const isVisualRefresh = useVisualRefresh();
8183

8284
const {
@@ -117,13 +119,13 @@ const InternalButtonDropdown = React.forwardRef(
117119
ref,
118120
() => ({
119121
focus(...args) {
120-
(isMainAction ? mainActionRef : triggerRef).current?.focus(...args);
122+
(hasMainAction ? mainActionRef : triggerRef).current?.focus(...args);
121123
},
122124
focusDropdownTrigger(...args) {
123125
triggerRef.current?.focus(...args);
124126
},
125127
}),
126-
[mainActionRef, triggerRef, isMainAction]
128+
[mainActionRef, triggerRef, hasMainAction]
127129
);
128130

129131
const clickHandler = () => {
@@ -170,6 +172,7 @@ const InternalButtonDropdown = React.forwardRef(
170172
formAction: 'none',
171173
nativeButtonAttributes: {
172174
'aria-haspopup': true,
175+
...nativeTriggerAttributes,
173176
},
174177
};
175178

@@ -222,7 +225,7 @@ const InternalButtonDropdown = React.forwardRef(
222225
})}
223226
</div>
224227
);
225-
} else if (isMainAction) {
228+
} else if (hasMainAction) {
226229
const { text, iconName, iconAlt, iconSvg, iconUrl, external, externalIconAriaLabel, ...mainActionProps } =
227230
mainAction;
228231
const mainActionIconProps = external
@@ -247,6 +250,8 @@ const InternalButtonDropdown = React.forwardRef(
247250
variant={variant}
248251
ariaLabel={mainActionAriaLabel}
249252
formAction="none"
253+
nativeAnchorAttributes={nativeMainActionAttributes?.anchor}
254+
nativeButtonAttributes={nativeMainActionAttributes?.button}
250255
>
251256
{text}
252257
</InternalButton>

0 commit comments

Comments
 (0)