Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Several fixes in the Chip #1943

Merged
merged 15 commits into from
Apr 15, 2024
13 changes: 11 additions & 2 deletions lib/src/chip/Chip.accessibility.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,22 @@ c-10.663,0-17.467,1.853-20.417,5.568c-2.949,3.711-4.428,10.23-4.428,19.558v31.11

describe("Chip component accessibility tests", () => {
it("Should not have basic accessibility issues", async () => {
const { container } = render(<DxcChip margin="small" prefixIcon={iconSVG} suffixIcon={iconSVG} label="Chip" />);
const { container } = render(
<DxcChip margin="small" prefixIcon={iconSVG} onClickPrefix={() => {}} suffixIcon={iconSVG} label="Chip" />
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("Should not have basic accessibility issues for disabled mode", async () => {
const { container } = render(
<DxcChip margin="small" prefixIcon={iconSVG} suffixIcon={iconSVG} label="Chip" disabled />
<DxcChip
margin="small"
prefixIcon={iconSVG}
onClickPrefix={() => {}}
suffixIcon={iconSVG}
label="Chip"
disabled
/>
);
const results = await axe(container);
expect(results).toHaveNoViolations();
Expand Down
114 changes: 68 additions & 46 deletions lib/src/chip/Chip.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ c-10.663,0-17.467,1.853-20.417,5.568c-2.949,3.711-4.428,10.23-4.428,19.558v31.11
</svg>
);

const smallIconSVG = (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" height="20" width="20">
<path d="m10 17-1.042-.938q-2.083-1.854-3.437-3.177-1.354-1.323-2.136-2.354Q2.604 9.5 2.302 8.646 2 7.792 2 6.896q0-1.854 1.271-3.125T6.396 2.5q1.021 0 1.979.438.958.437 1.625 1.229.667-.792 1.625-1.229.958-.438 1.979-.438 1.854 0 3.125 1.271T18 6.896q0 .896-.292 1.729-.291.833-1.073 1.854-.781 1.021-2.145 2.365-1.365 1.344-3.49 3.26Zm0-2.021q1.938-1.729 3.188-2.948 1.25-1.219 1.989-2.125.74-.906 1.031-1.614.292-.709.292-1.396 0-1.229-.833-2.063Q14.833 4 13.604 4q-.729 0-1.364.302-.636.302-1.094.844L10.417 6h-.834l-.729-.854q-.458-.542-1.114-.844Q7.083 4 6.396 4q-1.229 0-2.063.833-.833.834-.833 2.063 0 .687.271 1.364.271.678.989 1.573.719.896 1.98 2.125Q8 13.188 10 14.979Zm0-5.5Z" />
</svg>
);

const opinionatedTheme = {
chip: {
baseColor: "#e6e6e6",
Expand All @@ -50,33 +44,69 @@ const opinionatedTheme = {
export const Chromatic = () => (
<>
<ExampleContainer>
<Title title="Basic chip" theme="light" level={4} />
<DxcChip label="Default Chip" />
raquelarrojo marked this conversation as resolved.
Show resolved Hide resolved
<Title title="Default" theme="light" level={4} />
<DxcChip label="Default" />
</ExampleContainer>
<ExampleContainer>
<Title title="With prefix (SVG)" theme="light" level={4} />
<DxcChip label="Prefix" prefixIcon={iconSVG} />
</ExampleContainer>
<ExampleContainer>
<Title title="Chip with prefix SVG (small icon)" theme="light" level={4} />
<DxcChip label="Chip with prefix" prefixIcon={smallIconSVG} />
<Title title="With action prefix (SVG)" theme="light" level={4} />
<DxcChip label="Action prefix" prefixIcon={iconSVG} onClickPrefix={() => {}} />
</ExampleContainer>
<ExampleContainer>
<Title title="Chip with suffix SVG (large icon)" theme="light" level={4} />
<DxcChip label="Chip with suffix" suffixIcon={iconSVG} />
<Title title="Disabled action prefix (SVG)" theme="light" level={4} />
<DxcChip label="Disabled action prefix" prefixIcon={iconSVG} onClickPrefix={() => {}} disabled />
</ExampleContainer>
<ExampleContainer>
<Title title="Chip with prefix (SVG) and suffix (URL)" theme="light" level={4} />
<DxcChip label="Chip with prefix and suffix" prefixIcon={iconSVG} suffixIcon="filled_check_circle" />
<Title title="With suffix (Material icon)" theme="light" level={4} />
<DxcChip label="Suffix" suffixIcon="filled_check_circle" />
</ExampleContainer>
<ExampleContainer>
<Title title="Disabled chip" theme="light" level={4} />
<DxcChip label="Disabled" disabled prefixIcon={iconSVG} suffixIcon="filled_check_circle" />
<Title title="With action suffix (Material icon)" theme="light" level={4} />
<DxcChip label="Action suffix" suffixIcon="filled_check_circle" onClickSuffix={() => {}} />
</ExampleContainer>
<ExampleContainer>
<Title title="Chip with ellipsis" theme="light" level={4} />
<Title title="Action prefix and suffix" theme="light" level={4} />
<DxcChip
label="Action prefix and suffix"
prefixIcon="filled_check_circle"
onClickPrefix={() => {}}
suffixIcon={iconSVG}
/>
raquelarrojo marked this conversation as resolved.
Show resolved Hide resolved
</ExampleContainer>
<ExampleContainer>
<Title title="Prefix and action suffix" theme="light" level={4} />
<DxcChip
label="Prefix and action suffix"
prefixIcon="filled_check_circle"
suffixIcon={iconSVG}
onClickSuffix={() => {}}
/>
</ExampleContainer>
<ExampleContainer>
<Title title="Disabled action suffix (Material icon)" theme="light" level={4} />
<DxcChip label="Disabled action suffix" suffixIcon="filled_check_circle" onClickSuffix={() => {}} disabled />
</ExampleContainer>
<ExampleContainer>
<Title title="Disabled action prefix and suffix" theme="light" level={4} />
raquelarrojo marked this conversation as resolved.
Show resolved Hide resolved
<DxcChip
label="Disabled"
disabled
prefixIcon={iconSVG}
onClickPrefix={() => {}}
suffixIcon="filled_check_circle"
/>
</ExampleContainer>
<ExampleContainer>
<Title title="With ellipsis" theme="light" level={4} />
<div style={{ width: "200px" }}>
<DxcChip label="With ellipsis asdfasdf asdf asdfasdf asdf asdfasdf asdfasdf asdf asdf adfasrfasf afsdg afgasfg asdf asdf asdf asdf asdf asdf asdf afdg asfg asdfg asdf asdf asdf asdfasdf asd fas df asd asdf asdf asdfasd fg ssssssssssss ssss" />
</div>
</ExampleContainer>
<ExampleContainer>
<Title title="Chip with ellipsis and suffix" theme="light" level={4} />
<Title title="With ellipsis and suffix" theme="light" level={4} />
<div style={{ width: "200px" }}>
<DxcChip
suffixIcon={iconSVG}
Expand All @@ -85,7 +115,7 @@ export const Chromatic = () => (
</div>
</ExampleContainer>
<ExampleContainer>
<Title title="Chip with ellipsis and prefix" theme="light" level={4} />
<Title title="With ellipsis and prefix" theme="light" level={4} />
<div style={{ width: "200px" }}>
<DxcChip
prefixIcon={iconSVG}
Expand All @@ -94,10 +124,11 @@ export const Chromatic = () => (
</div>
</ExampleContainer>
<ExampleContainer>
<Title title="Chip with ellipsis, suffix and prefix" theme="light" level={4} />
<Title title="With ellipsis, action prefix and suffix" theme="light" level={4} />
<div style={{ width: "200px" }}>
<DxcChip
prefixIcon={iconSVG}
onClickPrefix={() => {}}
suffixIcon={iconSVG}
label="With ellipsis asdfasdf asdf asdfasdf asdf asdfasdf asdfasdf asdf asdf adfasrfasf afsdg afgasfg asdf asdf asdf asdf asdf asdf asdf afdg asfg asdfg asdf asdf asdf asdfasdf asd fas df asd asdf asdf asdfasdf"
/>
Expand Down Expand Up @@ -134,57 +165,48 @@ export const Chromatic = () => (
</ExampleContainer>
<Title title="Opinionated theme" theme="light" level={2} />
<ExampleContainer>
<Title title="Chip with prefix and suffix" theme="light" level={4} />
<Title title="With prefix and suffix" theme="light" level={4} />
<HalstackProvider theme={opinionatedTheme}>
<DxcChip label="Chip" prefixIcon={iconSVG} suffixIcon="filled_check_circle" />
<DxcChip label="Chip" prefixIcon={iconSVG} onClickPrefix={() => {}} suffixIcon="filled_check_circle" />
</HalstackProvider>
</ExampleContainer>
<ExampleContainer>
<Title title="Chip with prefix and suffix" theme="light" level={4} />
<Title title="With prefix and suffix" theme="light" level={4} />
<HalstackProvider theme={opinionatedTheme}>
<DxcChip label="Chip" disabled prefixIcon={iconSVG} suffixIcon="filled_check_circle" />
<DxcChip
label="Disabled"
disabled
prefixIcon={iconSVG}
suffixIcon="filled_check_circle"
onClickSuffix={() => {}}
/>
</HalstackProvider>
</ExampleContainer>
<ExampleContainer pseudoState="pseudo-hover">
<Title title="Hovered" theme="light" level={4} />
<HalstackProvider theme={opinionatedTheme}>
<DxcChip
label="Chip"
prefixIcon={iconSVG}
suffixIcon={iconSVG}
onClickPrefix={() => {}}
onClickSuffix={() => {}}
/>
<DxcChip label="Chip" prefixIcon={iconSVG} suffixIcon={iconSVG} onClickPrefix={() => {}} />
</HalstackProvider>
</ExampleContainer>
<ExampleContainer pseudoState="pseudo-active">
<Title title="Actived" theme="light" level={4} />
<HalstackProvider theme={opinionatedTheme}>
<DxcChip
label="Chip"
prefixIcon={iconSVG}
suffixIcon={iconSVG}
onClickPrefix={() => {}}
onClickSuffix={() => {}}
/>
<DxcChip label="Chip" prefixIcon={iconSVG} suffixIcon={iconSVG} onClickPrefix={() => {}} />
</HalstackProvider>
</ExampleContainer>
</>
);

const ChipPrefixFocused = () => (
<ExampleContainer>
<Title title="Chip with prefix" theme="light" level={4} />
<DxcChip
label="Chip with prefix"
prefixIcon={iconSVG}
onClickPrefix={() => {}}
/>
<Title title="With prefix" theme="light" level={4} />
<DxcChip label="Prefix" prefixIcon={iconSVG} onClickPrefix={() => {}} />
</ExampleContainer>
);
const ChipSuffixFocused = () => (
<ExampleContainer>
<Title title="Chip with suffix" theme="light" level={4} />
<DxcChip label="Chip with suffix" suffixIcon="filled_delete" onClickSuffix={() => {}} />
<Title title="With suffix" theme="light" level={4} />
<DxcChip label="Suffix" suffixIcon="filled_delete" onClickSuffix={() => {}} />
</ExampleContainer>
);

Expand Down
26 changes: 13 additions & 13 deletions lib/src/chip/Chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,28 @@ const DxcChip = ({

return (
<ThemeProvider theme={colorsTheme.chip}>
<Chip disabled={disabled} margin={margin}>
<Chip margin={margin}>
{prefixIcon && (
<IconContainer
role={typeof onClickPrefix === "function" ? "button" : undefined}
aria-label={typeof onClickPrefix === "function" ? "Prefix Action" : undefined}
disabled={disabled}
interactuable={typeof onClickPrefix === "function" && !disabled}
tabIndex={typeof onClickPrefix === "function" && !disabled ? tabIndex : -1}
onClick={() => onClickPrefix && !disabled && onClickPrefix()}
onClick={onClickPrefix}
>
{typeof prefixIcon === "string" ? <DxcIcon icon={prefixIcon} /> : prefixIcon}
</IconContainer>
)}
{label && <LabelContainer disabled={disabled}>{label}</LabelContainer>}
{label && <LabelContainer>{label}</LabelContainer>}
{suffixIcon && (
<IconContainer
role={typeof onClickSuffix === "function" ? "button" : undefined}
aria-label={typeof onClickSuffix === "function" ? "Suffix Action" : undefined}
disabled={disabled}
interactuable={typeof onClickSuffix === "function" && !disabled}
tabIndex={typeof onClickSuffix === "function" && !disabled ? tabIndex : -1}
onClick={() => !disabled && onClickSuffix?.()}
onClick={onClickSuffix}
>
{typeof suffixIcon === "string" ? <DxcIcon icon={suffixIcon} /> : suffixIcon}
</IconContainer>
Expand All @@ -54,15 +54,14 @@ const DxcChip = ({
const calculateWidth = (margin: ChipPropsType["margin"]) =>
`calc(100% - ${getMargin(margin, "left")} - ${getMargin(margin, "right")})`;

const Chip = styled.div<{ margin: ChipPropsType["margin"]; disabled: ChipPropsType["disabled"] }>`
const Chip = styled.div<{ margin: ChipPropsType["margin"] }>`
box-sizing: border-box;
display: inline-flex;
align-items: center;
gap: ${(props) => props.theme.iconSpacing};
min-height: 40px;
max-width: ${(props) => calculateWidth(props.margin)};
background-color: ${(props) =>
(props.disabled && props.theme.disabledBackgroundColor) || props.theme.backgroundColor};
background-color: ${(props) => props.theme.backgroundColor};
border-radius: ${(props) => props.theme.borderRadius};
border-width: ${(props) => props.theme.borderThickness};
border-style: ${(props) => props.theme.borderStyle};
Expand All @@ -81,29 +80,30 @@ const Chip = styled.div<{ margin: ChipPropsType["margin"]; disabled: ChipPropsTy
props.margin && typeof props.margin === "object" && props.margin.bottom ? spaces[props.margin.bottom] : ""};
margin-left: ${(props) =>
props.margin && typeof props.margin === "object" && props.margin.left ? spaces[props.margin.left] : ""};
cursor: ${({ disabled }) => disabled && "not-allowed"};
`;

const LabelContainer = styled.span<{ disabled: ChipPropsType["disabled"] }>`
const LabelContainer = styled.span`
font-size: ${(props) => props.theme.fontSize};
font-family: ${(props) => props.theme.fontFamily};
font-weight: ${(props) => props.theme.fontWeight};
font-style: ${(props) => props.theme.fontStyle};
color: ${(props) => (props.disabled ? props.theme.disabledFontColor : props.theme.fontColor)};
color: ${(props) => props.theme.fontColor};
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
`;

const IconContainer = styled.div<{
disabled: ChipPropsType["disabled"];
onClick?: ChipPropsType["onClickPrefix"];
interactuable: boolean;
}>`
display: flex;
border-radius: 0.25rem;
color: ${(props) => (props.disabled ? props.theme.disabledIconColor : props.theme.iconColor)};
${({ interactuable }) => interactuable && "cursor: pointer;"}

color: ${(props) =>
props.disabled && (props.onClick || props.onClick) ? props.theme.disabledIconColor : props.theme.iconColor};
cursor: ${(props) =>
props.disabled && (props.onClick || props.onClick) ? "not-allowed" : props.interactuable ? "pointer" : ""};
${(props) =>
props.interactuable &&
`
Expand Down
75 changes: 53 additions & 22 deletions lib/src/chip/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,60 @@ type Margin = {

type SVG = React.ReactNode & React.SVGProps<SVGSVGElement>;

type Props = {
type Icon = string | SVG;

type PrefixIconProps =
| {
/**
* Element or path used as icon to be placed before the chip label.
*/
prefixIcon: Icon;
/**
* This function will be called when the prefix is clicked.
*/
onClickPrefix: () => void;
/**
* Element or path used as icon to be placed after the chip label.
*/
suffixIcon?: Icon;
/**
* This function will be called when the suffix is clicked.
*/
onClickSuffix?: never;
/**
* If true, the action icon will be disabled.
*/
disabled?: boolean;
}
| {
prefixIcon?: Icon;
onClickPrefix?: never;
suffixIcon?: never;
onClickSuffix?: never;
disabled?: never;
};

type SuffixIconProps =
| {
suffixIcon: Icon;
onClickSuffix: () => void;
prefixIcon?: Icon;
onClickPrefix?: never;
disabled?: boolean;
}
| {
suffixIcon?: Icon;
onClickSuffix?: never;
prefixIcon?: never;
onClickPrefix?: never;
disabled?: never;
};

type CommonProps = {
/**
* Text to be placed on the chip.
*/
label?: string;
/**
* Element or path used as icon to be placed after the chip label.
*/
suffixIcon?: string | SVG;
/**
* Element or path used as icon to be placed before the chip label.
*/
prefixIcon?: string | SVG;
/**
* This function will be called when the suffix is clicked.
*/
onClickSuffix?: () => void;
/**
* This function will be called when the prefix is clicked.
*/
onClickPrefix?: () => void;
/**
* If true, the component will be disabled.
*/
disabled?: boolean;
/**
* Size of the margin to be applied to the component ('xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge').
* You can pass an object with 'top', 'bottom', 'left' and 'right' properties in order to specify different margin sizes.
Expand All @@ -42,6 +71,8 @@ type Props = {
* Value of the tabindex attribute.
*/
tabIndex?: number;
};
} & (PrefixIconProps | SuffixIconProps);
raquelarrojo marked this conversation as resolved.
Show resolved Hide resolved

type Props = (PrefixIconProps | SuffixIconProps) & CommonProps;

export default Props;
Loading
Loading