Skip to content

Commit 9309985

Browse files
committed
feat(chip): added support for noninteractable chips
Closes #1046
1 parent a621625 commit 9309985

File tree

7 files changed

+123
-7
lines changed

7 files changed

+123
-7
lines changed

packages/chip/src/Chip.tsx

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ export interface ChipProps extends ButtonAttributes {
100100
* `max-width` of the icon.
101101
*/
102102
disableIconTransition?: boolean;
103+
104+
/**
105+
* Boolean if the chip should render as a non-interactable element (`<span>`)
106+
* instead of a button. This can be used to just apply the chip styles.
107+
*
108+
* @since 2.6.0
109+
*/
110+
noninteractable?: boolean;
103111
}
104112

105113
const block = bem("rmd-chip");
@@ -127,6 +135,7 @@ export const Chip = forwardRef<HTMLButtonElement, ChipProps>(function Chip(
127135
contentClassName,
128136
disableContentWrap = false,
129137
selectedIcon: propSelectedIcon,
138+
noninteractable = false,
130139
disableIconTransition = false,
131140
...props
132141
},
@@ -135,8 +144,8 @@ export const Chip = forwardRef<HTMLButtonElement, ChipProps>(function Chip(
135144
const { ripples, className, handlers } = useInteractionStates({
136145
handlers: props,
137146
className: propClassName,
138-
disabled,
139-
enablePressedAndRipple: raisable,
147+
disabled: disabled || noninteractable,
148+
enablePressedAndRipple: raisable && !noninteractable,
140149
});
141150

142151
let content = children;
@@ -176,14 +185,19 @@ export const Chip = forwardRef<HTMLButtonElement, ChipProps>(function Chip(
176185

177186
const leading = leftIcon && !isHiddenIcon;
178187
const trailing = rightIcon;
188+
const Component = noninteractable ? "span" : "button";
189+
const buttonProps = {
190+
"aria-pressed": ariaPressed ?? (!!selected || undefined),
191+
type: "button",
192+
disabled,
193+
} as const;
179194

180195
return (
181-
<button
196+
<Component
197+
{...(noninteractable ? {} : buttonProps)}
182198
{...props}
183199
{...handlers}
184200
ref={ref}
185-
aria-pressed={ariaPressed ?? (!!selected || undefined)}
186-
type="button"
187201
className={cn(
188202
block({
189203
[theme]: true,
@@ -194,10 +208,10 @@ export const Chip = forwardRef<HTMLButtonElement, ChipProps>(function Chip(
194208
"leading-icon": leading && !trailing,
195209
"trailing-icon": trailing && !leading,
196210
surrounded: leading && trailing,
211+
noninteractable,
197212
}),
198213
className
199214
)}
200-
disabled={disabled}
201215
>
202216
<TextIconSpacing
203217
icon={leftIcon}
@@ -208,7 +222,7 @@ export const Chip = forwardRef<HTMLButtonElement, ChipProps>(function Chip(
208222
</TextIconSpacing>
209223
</TextIconSpacing>
210224
{ripples}
211-
</button>
225+
</Component>
212226
);
213227
});
214228

@@ -234,6 +248,7 @@ if (process.env.NODE_ENV !== "production") {
234248
selectedThemed: PropTypes.bool,
235249
children: PropTypes.node,
236250
selectedIcon: PropTypes.node,
251+
noninteractable: PropTypes.bool,
237252
disableIconTransition: PropTypes.bool,
238253
};
239254
} catch (e) {}

packages/chip/src/__tests__/Chip.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,30 @@ describe("Chip", () => {
7171
expect(container).toMatchSnapshot();
7272
expect(getIcon()).toBeNull();
7373
});
74+
75+
it("should render as a span when the noninteractable prop is enabled", () => {
76+
const props = {
77+
"data-testid": "chip",
78+
children: "Content",
79+
};
80+
const { getByTestId, rerender } = render(
81+
<Chip {...props} noninteractable />
82+
);
83+
84+
let chip = getByTestId("chip");
85+
expect(chip).toBeInstanceOf(HTMLSpanElement);
86+
expect(chip).not.toHaveAttribute("aria-pressed");
87+
expect(chip).not.toHaveAttribute("type");
88+
expect(chip).not.toHaveAttribute("disabled");
89+
expect(chip.className).toContain("rmd-chip--noninteractable");
90+
expect(chip).toMatchSnapshot();
91+
92+
rerender(<Chip {...props} />);
93+
chip = getByTestId("chip");
94+
expect(chip).toBeInstanceOf(HTMLButtonElement);
95+
expect(chip).toHaveAttribute("type", "button");
96+
expect(chip).not.toHaveAttribute("disabled");
97+
expect(chip.className).not.toContain("rmd-chip--noninteractable");
98+
expect(chip).toMatchSnapshot();
99+
});
74100
});

packages/chip/src/__tests__/__snapshots__/Chip.tsx.snap

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,39 @@ exports[`Chip should not render the selected icon until the selected prop is tru
8282
</div>
8383
`;
8484

85+
exports[`Chip should render as a span when the noninteractable prop is enabled 1`] = `
86+
<span
87+
class="rmd-chip rmd-chip--solid rmd-chip--noninteractable"
88+
data-testid="chip"
89+
>
90+
<span
91+
class="rmd-chip__content"
92+
>
93+
Content
94+
</span>
95+
<span
96+
class="rmd-ripple-container"
97+
/>
98+
</span>
99+
`;
100+
101+
exports[`Chip should render as a span when the noninteractable prop is enabled 2`] = `
102+
<button
103+
class="rmd-chip rmd-chip--solid"
104+
data-testid="chip"
105+
type="button"
106+
>
107+
<span
108+
class="rmd-chip__content"
109+
>
110+
Content
111+
</span>
112+
<span
113+
class="rmd-ripple-container"
114+
/>
115+
</button>
116+
`;
117+
85118
exports[`Chip should render correctly 1`] = `
86119
<div>
87120
<button

packages/chip/src/_mixins.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@
8282
max-width: 100%;
8383
position: relative;
8484

85+
&--noninteractable {
86+
@include rmd-states-theme-update-var(hover-color, transparent);
87+
88+
&:hover {
89+
// because the rmd-states-surface mixin uses
90+
// `:not(:disabled):not([aria-disabled='true'])`, have to use important or
91+
// an even more specific selector
92+
// sass-lint:disable no-important
93+
cursor: initial !important;
94+
}
95+
}
96+
8597
&--solid {
8698
@include rmd-elevation-transition(
8799
0,

packages/documentation/src/components/Demos/Chip/NoninteractableChips.md

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React, { ReactElement } from "react";
2+
import { Chip } from "@react-md/chip";
3+
import { Grid } from "@react-md/utils";
4+
5+
export default function NoninteractableChips(): ReactElement {
6+
return (
7+
<Grid phoneColumns={1} columns={2} wrapOnly>
8+
<Chip noninteractable theme="solid">
9+
Chip
10+
</Chip>
11+
<Chip noninteractable theme="outline">
12+
Chip
13+
</Chip>
14+
<Chip noninteractable theme="solid" disabled>
15+
Chip
16+
</Chip>
17+
<Chip noninteractable theme="outline" disabled>
18+
Chip
19+
</Chip>
20+
</Grid>
21+
);
22+
}

packages/documentation/src/components/Demos/Chip/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import choiceChips from "./ChoiceChips.md";
1414
import { ActionChips, actionChips } from "./ActionChips";
1515
import { InputChips, inputChips } from "./InputChips";
1616

17+
import NoninteractableChips from "./NoninteractableChips";
18+
import noninteractableChips from "./NoninteractableChips.md";
19+
1720
const demos = [
1821
{
1922
name: "Simple Chips",
@@ -42,6 +45,11 @@ const demos = [
4245
description: inputChips,
4346
children: <InputChips />,
4447
},
48+
{
49+
name: "Noninteractable Chips",
50+
description: noninteractableChips,
51+
children: <NoninteractableChips />,
52+
},
4553
];
4654

4755
export default function Chip(): ReactElement {

0 commit comments

Comments
 (0)