Skip to content
Open
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
62 changes: 62 additions & 0 deletions pages/radio-group/style-api-v2.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useState } from 'react';
import clsx from 'clsx';

import { RadioGroup, RadioGroupProps, SpaceBetween } from '~components';

import { SimplePage } from '../app/templates';

import styles from './style-api-v2.scss';

const plans: RadioGroupProps.RadioButtonDefinition[] = [
{ value: 'free', label: 'Free plan', description: 'For personal projects' },
{ value: 'pro', label: 'Pro plan', description: 'Best for growing teams' },
{ value: 'enterprise', label: 'Enterprise plan', description: 'Custom limits and support' },
];

const plansWithDisabled: RadioGroupProps.RadioButtonDefinition[] = [
plans[0],
plans[1],
{ ...plans[2], disabled: true },
];

interface StyledRadioGroupProps {
ariaLabel: string;
items: RadioGroupProps.RadioButtonDefinition[];
readOnly?: boolean;
direction?: RadioGroupProps['direction'];
}

function StyledRadioGroup({ ariaLabel, items, readOnly, direction }: StyledRadioGroupProps) {
const [value, setValue] = useState('pro');
Comment thread
vvaliyev marked this conversation as resolved.
Resolved
return (
<RadioGroup
ariaLabel={ariaLabel}
value={value}
items={items}
readOnly={readOnly}
direction={direction}
onChange={({ detail }) => setValue(detail.value)}
{...{
styleClassNames: {
radioButton: { control: clsx(styles['radio-control'], readOnly && styles['radio-control-readonly']) },
},
}}
/>
);
}

export default function () {
return (
<SimplePage title="RadioGroup - Style API v2" screenshotArea={{}}>
<SpaceBetween size="l" direction="horizontal">
<StyledRadioGroup ariaLabel="Default" items={plansWithDisabled} />
<StyledRadioGroup ariaLabel="Read-only" items={plans} readOnly={true} />
</SpaceBetween>
<SpaceBetween size="l" direction="horizontal">
<StyledRadioGroup ariaLabel="Horizontal" items={plans} direction="horizontal" />
</SpaceBetween>
</SimplePage>
);
}
37 changes: 37 additions & 0 deletions pages/radio-group/style-api-v2.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

@use '~components/styling/radio-button';
@use '../style-api/palette' as palette;

.radio-control {
@include radio-button.control(
$fill-color: palette.$control-bg,
$border-color: palette.$border-accent,
$focus-ring-color: palette.$accent,
$focus-ring-width: 3px,
$focus-ring-radius: 50%
);

&:has(:checked) {
@include radio-button.control($fill-color: palette.$control-bg-checked, $indicator-color: palette.$control-fg);
}

&:has(:disabled) {
@include radio-button.control(
$fill-color: palette.$control-bg-disabled,
$border-color: palette.$border-disabled,
$indicator-color: palette.$control-fg-disabled
);
}

&.radio-control-readonly {
@include radio-button.control(
$fill-color: palette.$surface-alt,
$border-color: palette.$border,
$indicator-color: palette.$text-muted
);
}
}
2 changes: 1 addition & 1 deletion src/internal/components/radio-button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface InternalRadioButtonProps extends RadioButtonProps, InternalBase
}

// Style API v2
interface StyleClassNames {
export interface StyleClassNames {
control?: string;
}

Expand Down
2 changes: 2 additions & 0 deletions src/internal/components/radio-button/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ $radio-size: awsui.$size-control;

$_control-props: style-api.combine((fill-color, border-color, indicator-color), style-api.$focus-ring);
$_control-tokens: style-api.resolve($_control-props, carrier);
// Registered here, next to the carrier, so every component that renders this control loads the registration.
@include style-api.register($_control-tokens);

.radio-control {
@include style-api.carriers($_control-tokens);
Expand Down
3 changes: 1 addition & 2 deletions src/radio-button/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
@use '../internal/styles' as styles;
@use '../internal/styles/style-api' as style-api;

// The control slot is painted by the shared internal radio button, which reads the same token map.
// The control slot is painted by the shared internal radio button, which reads the same token map and registers it.
$control-props: style-api.combine((fill-color, border-color, indicator-color), style-api.$focus-ring);
$control-tokens: style-api.resolve($control-props, carrier);
@include style-api.register($control-tokens);
@include style-api.docs('control', $control-tokens);

.radio-button {
Expand Down
35 changes: 35 additions & 0 deletions src/radio-group/__tests__/style-api.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { render } from '@testing-library/react';

import RadioGroup, { RadioGroupProps } from '../../../lib/components/radio-group';
import createWrapper from '../../../lib/components/test-utils/dom';

const items: RadioGroupProps.RadioButtonDefinition[] = [
{ value: 'free', label: 'Free plan' },
{ value: 'pro', label: 'Pro plan' },
{ value: 'enterprise', label: 'Enterprise plan', disabled: true },
];

function renderRadioGroup(styleClassNames: Record<string, unknown>) {
const { container } = render(<RadioGroup ariaLabel="Plan" value="pro" items={items} {...{ styleClassNames }} />);
return { container, wrapper: createWrapper(container).findRadioGroup()! };
}

describe('RadioGroup Style API v2', () => {
test('forwards the radio button classes to every radio', () => {
const { container, wrapper } = renderRadioGroup({ radioButton: { control: 'a', unknown: 'b' }, unknown: 'c' });
const buttons = wrapper.findButtons();
expect(buttons).toHaveLength(3);
for (const button of buttons) {
expect(button.findNativeInput().getElement().parentElement).toHaveClass('a');
}
expect(container.querySelector('.b, .c')).toBeNull();
});

test('does not leak the styleClassNames prop to the DOM', () => {
const { container } = renderRadioGroup({ radioButton: { control: 'a' } });
expect(container.querySelector('[styleClassNames]')).toBeNull();
});
});
14 changes: 12 additions & 2 deletions src/radio-group/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useUniqueId } from '@cloudscape-design/component-toolkit/internal';
import { getAnalyticsMetadataAttribute } from '@cloudscape-design/component-toolkit/internal/analytics-metadata';

import { getBaseProps } from '../internal/base-component';
import RadioButton from '../internal/components/radio-button';
import RadioButton, { StyleClassNames as RadioButtonStyleClassNames } from '../internal/components/radio-button';
import { useFormFieldContext } from '../internal/context/form-field-context';
import { fireNonCancelableEvent } from '../internal/events';
import useRadioGroupForwardFocus from '../internal/hooks/forward-focus/radio-group';
Expand All @@ -19,7 +19,15 @@ import analyticsSelectors from './analytics-metadata/styles.css.js';
import styles from './styles.css.js';
import testUtilStyles from './test-classes/styles.css.js';

type InternalRadioGroupProps = RadioGroupProps & InternalBaseComponentProps;
type InternalRadioGroupProps = RadioGroupProps &
InternalBaseComponentProps & {
styleClassNames?: StyleClassNames;
Comment thread
vvaliyev marked this conversation as resolved.
Resolved
};

// Style API v2
export interface StyleClassNames {
radioButton?: RadioButtonStyleClassNames;
}

const InternalRadioGroup = React.forwardRef(
(
Expand All @@ -35,6 +43,7 @@ const InternalRadioGroup = React.forwardRef(
__internalRootRef,
style,
direction,
styleClassNames,
...props
}: InternalRadioGroupProps,
ref: React.Ref<RadioGroupProps.Ref>
Expand Down Expand Up @@ -83,6 +92,7 @@ const InternalRadioGroup = React.forwardRef(
controlId={item.controlId}
readOnly={readOnly}
style={style}
styleClassNames={styleClassNames?.radioButton}
{...getAnalyticsMetadataAttribute(
!item.disabled && !readOnly
? {
Expand Down
3 changes: 3 additions & 0 deletions src/radio-group/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

@use '../internal/styles' as styles;
@use '../internal/styles/tokens' as awsui;
@use '../internal/styles/style-api' as style-api;

@include style-api.docs-forward('radioButton', 'radio-button');

.radio-group {
@include styles.styles-reset;
Expand Down
Loading