Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15355,6 +15355,28 @@ use the \`id\` attribute, consider setting it on a parent element instead.",
"optional": true,
"type": "string",
},
{
"description": "Attributes to add to the native element.
Some attributes will be automatically combined with internal attribute values:
- \`className\` will be appended.
- Event handlers will be chained, unless the default is prevented.

We do not support using this attribute to apply custom styling.",
"inlineType": {
"name": "Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "children"> & Record<\`data-\${string}\`, string>",
"type": "union",
"values": [
"Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "children">",
"Record<\`data-\${string}\`, string>",
],
},
"name": "nativeAttributes",
"optional": true,
"systemTags": [
"core",
],
"type": "Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "children"> & Record<\`data-\${string}\`, string>",
},
{
"description": "Adds a \`rel\` attribute to the link. If the \`rel\` property is provided, it overrides the default behaviour.
By default, the component sets the \`rel\` attribute to "noopener noreferrer" when \`external\` is \`true\` or \`target\` is \`"_blank"\`.",
Expand Down Expand Up @@ -25423,6 +25445,28 @@ use the \`id\` attribute, consider setting it on a parent element instead.",
"optional": true,
"type": "string",
},
{
"description": "Attributes to add to the native \`input\` element.
Some attributes will be automatically combined with internal attribute values:
- \`className\` will be appended.
- Event handlers will be chained, unless the default is prevented.

We do not support using this attribute to apply custom styling.",
"inlineType": {
"name": "Omit<React.InputHTMLAttributes<HTMLInputElement>, "children"> & Record<\`data-\${string}\`, string>",
"type": "union",
"values": [
"Omit<React.InputHTMLAttributes<HTMLInputElement>, "children">",
"Record<\`data-\${string}\`, string>",
],
},
"name": "nativeInputAttributes",
"optional": true,
"systemTags": [
"core",
],
"type": "Omit<React.InputHTMLAttributes<HTMLInputElement>, "children"> & Record<\`data-\${string}\`, string>",
},
{
"description": "Specifies if the control is read-only, which prevents the
user from modifying the value. Should be used only inside forms.
Expand Down Expand Up @@ -25893,28 +25937,6 @@ It prevents users from clicking the button, but it can still be focused.",
"optional": true,
"type": "string",
},
{
"description": "Attributes to add to the native \`a\` element (when \`href\` is provided).
Some attributes will be automatically combined with internal attribute values:
- \`className\` will be appended.
- Event handlers will be chained, unless the default is prevented.

We do not support using this attribute to apply custom styling.",
"inlineType": {
"name": "Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "children"> & Record<\`data-\${string}\`, string>",
"type": "union",
"values": [
"Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "children">",
"Record<\`data-\${string}\`, string>",
],
},
"name": "nativeAnchorAttributes",
"optional": true,
"systemTags": [
"core",
],
"type": "Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "children"> & Record<\`data-\${string}\`, string>",
},
{
"description": "Attributes to add to the native \`button\` element.
Some attributes will be automatically combined with internal attribute values:
Expand Down
18 changes: 18 additions & 0 deletions src/link/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -424,3 +424,21 @@ describe('Style API', () => {
expect(getComputedStyle(link).getPropertyValue(customCssProps.styleFocusRingBorderWidth)).toBe('4px');
});
});

describe('native attributes', () => {
it('adds native attributes', () => {
const { container } = render(<Link href="#" nativeAttributes={{ 'data-testid': 'my-test-id' }} />);
expect(container.querySelectorAll('[data-testid="my-test-id"]')).toHaveLength(1);
expect(container.querySelectorAll('a[data-testid="my-test-id"]')).toHaveLength(1);
});
it('adds native attributes (button link)', () => {
const { container } = render(<Link nativeAttributes={{ 'data-testid': 'my-test-id' }} />);
expect(container.querySelectorAll('[data-testid="my-test-id"]')).toHaveLength(1);
expect(container.querySelectorAll('a[data-testid="my-test-id"]')).toHaveLength(1);
});
it('concatenates class names', () => {
const { container } = render(<Link href="#" nativeAttributes={{ className: 'additional-class' }} />);
expect(container.firstChild).toHaveClass(styles.link);
expect(container.firstChild).toHaveClass('additional-class');
});
});
16 changes: 16 additions & 0 deletions src/link/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import {
ClickDetail as _ClickDetail,
NonCancelableEventHandler,
} from '../internal/events';
/**
* @awsuiSystem core
*/
import { NativeAttributes } from '../internal/utils/with-native-attributes';

export interface LinkProps extends BaseComponentProps {
/**
Expand Down Expand Up @@ -111,6 +115,18 @@ export interface LinkProps extends BaseComponentProps {
* @awsuiSystem core
*/
style?: LinkProps.Style;

/**
* Attributes to add to the native element.
* Some attributes will be automatically combined with internal attribute values:
* - `className` will be appended.
* - Event handlers will be chained, unless the default is prevented.
*
* We do not support using this attribute to apply custom styling.
*
* @awsuiSystem core
*/
nativeAttributes?: NativeAttributes<React.AnchorHTMLAttributes<HTMLAnchorElement>>;
}

export namespace LinkProps {
Expand Down
16 changes: 12 additions & 4 deletions src/link/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { InternalBaseComponentProps } from '../internal/hooks/use-base-component
import { useVisualRefresh } from '../internal/hooks/use-visual-mode';
import { KeyCode } from '../internal/keycode';
import { checkSafeUrl } from '../internal/utils/check-safe-url';
import WithNativeAttributes from '../internal/utils/with-native-attributes';
import { LinkProps } from './interfaces';
import { getLinkStyles } from './style';

Expand All @@ -50,6 +51,7 @@ const InternalLink = React.forwardRef(
onFollow,
onClick,
children,
nativeAttributes,
__internalRootRef,
style,
...props
Expand Down Expand Up @@ -210,29 +212,35 @@ const InternalLink = React.forwardRef(

if (isButton) {
return (
<a
<WithNativeAttributes
{...sharedProps}
tag="a"
componentName="Link"
nativeAttributes={nativeAttributes}
role="button"
tabIndex={tabIndex}
onKeyDown={handleButtonKeyDown}
onClick={handleButtonClick}
>
{content}
</a>
</WithNativeAttributes>
);
}

return (
<a
<WithNativeAttributes
{...sharedProps}
tag="a"
componentName="Link"
nativeAttributes={nativeAttributes}
tabIndex={tabIndex}
target={anchorTarget}
rel={anchorRel}
href={href}
onClick={handleLinkClick}
>
{content}
</a>
</WithNativeAttributes>
);
}
);
Expand Down
19 changes: 19 additions & 0 deletions src/toggle-button/__tests__/toggle-button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import createWrapper from '../../../lib/components/test-utils/dom';
import ToggleButton, { ToggleButtonProps } from '../../../lib/components/toggle-button';
import { getToggleIcon } from '../../../lib/components/toggle-button/util';

import styles from '../../../lib/components/button/styles.css.js';

jest.mock('@cloudscape-design/component-toolkit/internal', () => ({
...jest.requireActual('@cloudscape-design/component-toolkit/internal'),
warnOnce: jest.fn(),
Expand Down Expand Up @@ -133,4 +135,21 @@ describe('ToggleButton Component', () => {
expect(getToggleIcon(true, 'star')).toBe('star');
});
});

describe('native attributes', () => {
it('adds native attributes', () => {
const { container } = render(
<ToggleButton pressed={true} nativeButtonAttributes={{ 'data-testid': 'my-test-id' }} />
);
expect(container.querySelectorAll('[data-testid="my-test-id"]')).toHaveLength(1);
expect(container.querySelectorAll('button[data-testid="my-test-id"]')).toHaveLength(1);
});
it('concatenates class names', () => {
const { container } = render(
<ToggleButton pressed={true} nativeButtonAttributes={{ className: 'additional-class' }} />
);
expect(container.firstChild).toHaveClass(styles.button);
expect(container.firstChild).toHaveClass('additional-class');
});
});
});
2 changes: 2 additions & 0 deletions src/toggle-button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const ToggleButton = React.forwardRef(
ariaDescribedby,
ariaControls,
pressed = false,
nativeButtonAttributes,
onChange,
...props
}: ToggleButtonProps,
Expand Down Expand Up @@ -65,6 +66,7 @@ const ToggleButton = React.forwardRef(
pressedIconUrl={pressedIconUrl}
pressedIconSvg={pressedIconSvg}
pressed={pressed}
nativeButtonAttributes={nativeButtonAttributes}
onChange={onChange}
>
{children}
Expand Down
2 changes: 1 addition & 1 deletion src/toggle-button/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { IconProps } from '../icon/interfaces';
import { BaseComponentProps } from '../internal/base-component';
import { NonCancelableEventHandler } from '../internal/events';

export interface ToggleButtonProps extends BaseComponentProps, BaseButtonProps {
export interface ToggleButtonProps extends BaseComponentProps, Omit<BaseButtonProps, 'nativeAnchorAttributes'> {
/** Determines the general styling of the toggle button as follows:
* * `normal` for secondary buttons.
* * `icon` to display an icon only (no text).
Expand Down
2 changes: 2 additions & 0 deletions src/toggle-button/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const InternalToggleButton = React.forwardRef(
iconUrl: defaultIconUrl,
pressedIconUrl,
variant,
nativeButtonAttributes,
onChange,
className,
...rest
Expand Down Expand Up @@ -60,6 +61,7 @@ export const InternalToggleButton = React.forwardRef(
}}
{...rest}
ref={ref}
nativeButtonAttributes={nativeButtonAttributes}
/>
);
}
Expand Down
14 changes: 14 additions & 0 deletions src/toggle/__tests__/toggle.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,17 @@ test('all style api properties', () => {
expect(getComputedStyle(toggleHandle).getPropertyValue('background-color')).toBe('blue');
expect(getComputedStyle(toggleLabel).getPropertyValue('color')).toBe('orange');
});

describe('native attributes', () => {
it('adds native attributes', () => {
const { container } = render(<Toggle checked={true} nativeInputAttributes={{ 'data-testid': 'my-test-id' }} />);
expect(container.querySelectorAll('[data-testid="my-test-id"]')).toHaveLength(1);
expect(container.querySelectorAll('input[data-testid="my-test-id"]')).toHaveLength(1);
});
it('concatenates class names', () => {
const { container } = render(<Toggle checked={true} nativeInputAttributes={{ className: 'additional-class' }} />);
const input = container.querySelector('input');
expect(input).toHaveClass(abstractSwitchStyles['native-input']);
expect(input).toHaveClass('additional-class');
});
});
16 changes: 16 additions & 0 deletions src/toggle/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import React from 'react';

import { BaseCheckboxProps } from '../checkbox/base-checkbox';
import { NonCancelableEventHandler } from '../internal/events';
/**
* @awsuiSystem core
*/
import { NativeAttributes } from '../internal/utils/with-native-attributes';

export interface ToggleProps extends BaseCheckboxProps {
/**
Expand All @@ -22,6 +26,18 @@ export interface ToggleProps extends BaseCheckboxProps {
* @awsuiSystem core
*/
style?: ToggleProps.Style;

/**
* Attributes to add to the native `input` element.
* Some attributes will be automatically combined with internal attribute values:
* - `className` will be appended.
* - Event handlers will be chained, unless the default is prevented.
*
* We do not support using this attribute to apply custom styling.
*
* @awsuiSystem core
*/
nativeInputAttributes?: NativeAttributes<React.InputHTMLAttributes<HTMLInputElement>>;
}

export namespace ToggleProps {
Expand Down
7 changes: 6 additions & 1 deletion src/toggle/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useFormFieldContext } from '../internal/context/form-field-context';
import { fireNonCancelableEvent } from '../internal/events';
import useForwardFocus from '../internal/hooks/forward-focus';
import { InternalBaseComponentProps } from '../internal/hooks/use-base-component';
import WithNativeAttributes from '../internal/utils/with-native-attributes';
import { GeneratedAnalyticsMetadataToggleComponent } from './analytics-metadata/interfaces';
import { ToggleProps } from './interfaces';
import { getAbstractSwitchStyles, getStyledControlStyle } from './style';
Expand All @@ -39,6 +40,7 @@ const InternalToggle = React.forwardRef<ToggleProps.Ref, InternalToggleProps>(
onFocus,
onBlur,
onChange,
nativeInputAttributes,
__internalRootRef,
style,
__injectAnalyticsComponentMetadata,
Expand Down Expand Up @@ -89,8 +91,11 @@ const InternalToggle = React.forwardRef<ToggleProps.Ref, InternalToggleProps>(
ariaDescribedby={ariaDescribedby}
ariaControls={ariaControls}
nativeControl={nativeControlProps => (
<input
<WithNativeAttributes
{...nativeControlProps}
tag="input"
componentName="Toggle"
nativeAttributes={nativeInputAttributes}
ref={checkboxRef}
type="checkbox"
checked={checked}
Expand Down
Loading