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
5 changes: 5 additions & 0 deletions .changeset/proud-items-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/polaris': minor
---

Added a `disabled` prop to `ResourceItem`
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@
margin-right: 0;
}

.disabled {
cursor: default;
color: var(--p-color-text-secondary);

&:hover {
background-color: transparent;
}
}

// Item actions
.Actions {
> * {
Expand Down
66 changes: 66 additions & 0 deletions polaris-react/src/components/ResourceItem/ResourceItem.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,69 @@ export function WithVerticalAlignment() {
</LegacyCard>
);
}

export function WithDisabledState() {
const [selectedItems, setSelectedItems] = useState<
ResourceListProps['selectedItems']
>([]);

return (
<LegacyCard>
<ResourceList
resourceName={{singular: 'customer', plural: 'customers'}}
selectable
selectedItems={selectedItems}
onSelectionChange={setSelectedItems}
items={[
{
id: '145',
url: '#',
avatarSource:
'https://burst.shopifycdn.com/photos/freelance-designer-working-on-laptop.jpg?width=746',
name: 'Yi So-Yeon',
location: 'Gwangju, South Korea',
},
{
id: '146',
url: '#',
avatarSource:
'https://burst.shopifycdn.com/photos/woman-standing-in-front-of-yellow-background.jpg?width=746',
name: 'Jane Smith',
location: 'Manhattan, New York',
},
{
id: '147',
url: '#',
avatarSource:
'https://burst.shopifycdn.com/photos/relaxing-in-headphones.jpg?width=746',
name: 'Grace Baker',
location: 'Los Angeles, California',
},
]}
renderItem={(item) => {
const {id, url, avatarSource, name, location} = item;

return (
<ResourceItem
disabled={id === '145'}
id={id}
url={url}
media={
<Avatar customer size="md" name={name} source={avatarSource} />
}
accessibilityLabel={`View details for ${name}`}
name={name}
>
<h3>
<Text fontWeight="bold" as="span">
{name}
</Text>
</h3>
<div>{location}</div>
</ResourceItem>
);
}}
/>
</LegacyCard>
);
}
13 changes: 9 additions & 4 deletions polaris-react/src/components/ResourceItem/ResourceItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import styles from './ResourceItem.module.scss';
type Alignment = 'leading' | 'trailing' | 'center' | 'fill' | 'baseline';

interface BaseProps {
/** Whether or not interaction is disabled */
disabled?: boolean;
/** Visually hidden text for screen readers used for item link */
accessibilityLabel?: string;
/** Individual item name used by various text labels */
Expand Down Expand Up @@ -159,6 +161,7 @@ class BaseResourceItem extends Component<CombinedProps, State> {
dataHref,
breakpoints,
onMouseOver,
disabled,
} = this.props;

const {actionsMenuVisible, focused, focusedInner, selected} = this.state;
Expand All @@ -183,7 +186,7 @@ class BaseResourceItem extends Component<CombinedProps, State> {
label={checkboxAccessibilityLabel}
labelHidden
checked={selected}
disabled={loading}
disabled={loading || disabled}
bleedInlineStart="300"
bleedInlineEnd="300"
bleedBlockStart="300"
Expand Down Expand Up @@ -219,6 +222,7 @@ class BaseResourceItem extends Component<CombinedProps, State> {
selectMode && styles.selectMode,
persistActions && styles.persistActions,
focusedInner && styles.focusedInner,
disabled && styles.disabled,
);

const listItemClassName = classNames(
Expand Down Expand Up @@ -351,15 +355,15 @@ class BaseResourceItem extends Component<CombinedProps, State> {
<div
ref={this.setNode}
className={className}
onClick={this.handleClick}
onClick={disabled ? () => {} : this.handleClick}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onKeyUp={this.handleKeyUp}
onMouseOver={onMouseOver}
onMouseOut={this.handleMouseOut}
data-href={url}
>
{accessibleMarkup}
{disabled ? null : accessibleMarkup}
{containerMarkup}
</div>
</div>
Expand Down Expand Up @@ -458,12 +462,13 @@ class BaseResourceItem extends Component<CombinedProps, State> {
// This fires onClick when there is a URL on the item
private handleKeyUp = (event: React.KeyboardEvent<HTMLElement>) => {
const {
disabled,
onClick = noop,
context: {selectMode},
} = this.props;
const {key} = event;

if (key === 'Enter' && this.props.url && !selectMode) {
if (key === 'Enter' && this.props.url && !selectMode && !disabled) {
onClick();
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,22 @@ describe('<ResourceItem />', () => {

expect(element).toContainReactComponent('div', {'data-href': url} as any);
});

it('does not render an <UnstyledLink /> when disabled prop is true', () => {
const element = mountWithApp(
<ResourceListContext.Provider value={mockDefaultContext}>
<ResourceItem
id="itemId"
url={url}
onClick={noop}
accessibilityLabel={ariaLabel}
disabled
/>
</ResourceListContext.Provider>,
);

expect(element).not.toContainReactComponent(UnstyledLink);
});
});

describe('external', () => {
Expand Down Expand Up @@ -388,6 +404,38 @@ describe('<ResourceItem />', () => {
expect(onClick).not.toHaveBeenCalled();
});

it('does not call onClick when hitting keyUp on the item when onClick exists, url exists and is disabled', () => {
const onClick = jest.fn();
const wrapper = mountWithApp(
<ResourceListContext.Provider value={mockSelectModeContext}>
<ResourceItem id={itemId} url="#" onClick={onClick} disabled />
</ResourceListContext.Provider>,
);

findResourceItem(wrapper)!.trigger('onKeyUp', {key: 'Enter'});
expect(onClick).not.toHaveBeenCalled();
});

it('does not call onClick when clicking on the item when onClick exists and is disabled', () => {
const onClick = jest.fn();
const wrapper = mountWithApp(
<ResourceListContext.Provider value={mockDefaultContext}>
<ResourceItem
id={itemId}
onClick={onClick}
accessibilityLabel={ariaLabel}
disabled
/>
</ResourceListContext.Provider>,
);

findResourceItem(wrapper)!.trigger('onClick', {
stopPropagation: () => {},
nativeEvent: {},
});
expect(onClick).not.toHaveBeenCalledWith(itemId);
});

it('calls window.open on ctrlKey + click', () => {
const wrapper = mountWithApp(
<ResourceListContext.Provider value={mockDefaultContext}>
Expand Down Expand Up @@ -448,6 +496,15 @@ describe('<ResourceItem />', () => {
false,
);
});

it('renders a disabled Checkbox if the item is disabled', () => {
const wrapper = mountWithApp(
<ResourceListContext.Provider value={mockSelectableContext}>
<ResourceItem id={selectedItemId} url={url} disabled />
</ResourceListContext.Provider>,
);
expect(wrapper).toContainReactComponent(Checkbox, {disabled: true});
});
});

describe('SelectMode', () => {
Expand Down