Skip to content

Commit

Permalink
feat: added an example in storybook on how to use Selectable Tags (#1…
Browse files Browse the repository at this point in the history
…7798)

* feat: added an example in storybook on how to use selectable tags

* test: ypdated snapshots

* test: fixed avt test

* fix: fixed a11y issue

* fix: fixed spelling

* test: added tests for new props
  • Loading branch information
guidari authored Oct 23, 2024
1 parent b543079 commit 8a75cb5
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 24 deletions.
6 changes: 6 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6992,6 +6992,12 @@ Map {
"id": Object {
"type": "string",
},
"onChange": Object {
"type": "func",
},
"onClick": Object {
"type": "func",
},
"renderIcon": Object {
"args": Array [
Array [
Expand Down
58 changes: 46 additions & 12 deletions packages/react/src/components/Tag/InteractiveTag.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,54 @@ export default {
};

export const Selectable = (args) => {
const tags = [
{
id: 1,
text: 'Tag content with a long text description',
},
{
id: 2,
text: 'Tag content 1',
},
{
id: 3,
text: 'Tag content 2',
},
{
id: 4,
text: 'Tag content 3',
},
];

const [selectedTags, setSelectedTags] = useState([
{
id: 2,
text: 'Tag content 1',
},
]);

const handleChange = (tag, selected) => {
const nextSelectedTags = selected
? [...selectedTags, tag]
: selectedTags.filter((t) => t.id !== tag.id);

console.log('Selected tags array: ', nextSelectedTags);
setSelectedTags(nextSelectedTags);
};

return (
<div aria-label="Selectable tags" role="group">
<SelectableTag
renderIcon={Asleep}
text="Tag content with a long text description"
className="some-class"
{...args}
/>
<SelectableTag
renderIcon={Asleep}
text="Tag content"
className="some-class"
{...args}
/>
{tags.map((tag, index) => (
<SelectableTag
key={index}
renderIcon={Asleep}
text={tag.text}
className="some-class"
selected={selectedTags.find((t) => t.id === tag.id)}
onChange={(selected) => handleChange(tag, selected)}
{...args}
/>
))}
</div>
);
};
Expand Down
42 changes: 33 additions & 9 deletions packages/react/src/components/Tag/SelectableTag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ export interface SelectableTagBaseProps {
*/
renderIcon?: React.ElementType;

/**
* Provide an optional hook that is called when selected is changed
*/
onChange?: (selected: boolean) => void;

/**
* Provide an optional function to be called when the tag is clicked.
*/
onClick?: (e: Event) => void;

/**
* Specify the state of the selectable tag.
*/
Expand Down Expand Up @@ -65,6 +75,8 @@ const SelectableTag = <T extends React.ElementType>({
disabled,
id,
renderIcon,
onChange,
onClick,
selected = false,
size,
text,
Expand All @@ -91,9 +103,11 @@ const SelectableTag = <T extends React.ElementType>({
`${prefix}--tag-label-tooltip`
);

// Removing onClick from the spread operator
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { onClick, ...otherProps } = other;
const handleClick = (e: Event) => {
setSelectedTag(!selectedTag);
onChange?.(!selectedTag);
onClick?.(e);
};

if (isEllipsisApplied) {
return (
Expand All @@ -104,15 +118,15 @@ const SelectableTag = <T extends React.ElementType>({
leaveDelayMs={0}
onMouseEnter={() => false}>
<Tag
aria-pressed={selectedTag}
aria-pressed={selectedTag !== false}
ref={tagRef}
size={size}
renderIcon={renderIcon}
disabled={disabled}
className={tagClasses}
id={tagId}
onClick={() => setSelectedTag(!selectedTag)}
{...otherProps}>
onClick={handleClick}
{...other}>
<Text title={text} className={`${prefix}--tag__label`}>
{text}
</Text>
Expand All @@ -123,15 +137,15 @@ const SelectableTag = <T extends React.ElementType>({

return (
<Tag
aria-pressed={selectedTag}
aria-pressed={selectedTag !== false}
ref={tagRef}
size={size}
renderIcon={renderIcon}
disabled={disabled}
className={tagClasses}
id={tagId}
onClick={() => setSelectedTag(!selectedTag)}
{...otherProps}>
onClick={handleClick}
{...other}>
<Text title={text} className={`${prefix}--tag__label`}>
{text}
</Text>
Expand Down Expand Up @@ -161,6 +175,16 @@ SelectableTag.propTypes = {
*/
renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),

/**
* Provide an optional hook that is called when selected is changed
*/
onChange: PropTypes.func,

/**
* Provide an optional function to be called when the tag is clicked.
*/
onClick: PropTypes.func,

/**
* Specify the state of the selectable tag.
*/
Expand Down
30 changes: 27 additions & 3 deletions packages/react/src/components/Tag/Tag-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,38 @@ describe('Tag', () => {
it('should select the selectable tag', async () => {
const { container } = render(<SelectableTag text="Tag content" />);

const selectableTag = container.querySelector(
`.${prefix}--tag--selectable`
);
const selectableTag = container.firstChild;

await userEvent.click(selectableTag);
expect(selectableTag).toHaveAttribute('aria-pressed', 'true');
expect(selectableTag).toHaveClass(`${prefix}--tag--selectable-selected`);
});

it('should call onChange', async () => {
const onChange = jest.fn();

const { container } = render(
<SelectableTag text="Tag content" onChange={onChange} />
);

const selectableTag = container.firstChild;

await userEvent.click(selectableTag);
expect(onChange).toHaveBeenCalledTimes(1);
});

it('should call onClick', async () => {
const onClick = jest.fn();

const { container } = render(
<SelectableTag text="Tag content" onClick={onClick} />
);

const selectableTag = container.firstChild;

await userEvent.click(selectableTag);
expect(onClick).toHaveBeenCalledTimes(1);
});
});

describe('Skeleton Tag', () => {
Expand Down
113 changes: 113 additions & 0 deletions packages/react/src/components/Tag/Tag.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,64 @@ When to use:

<Canvas of={InteractiveTagStories.Dismissible} />

Here it is a sample code to help you get started.

```js
const tags = [
{
type: 'red',
text: 'Tag content with a long text description',
tagTitle: 'Provide a custom title to the tag',
},
{
type: 'magenta',
text: 'Tag content 1',
},
{
type: 'purple',
text: 'Tag content 2',
},
];

const [renderedTags, setRenderedTags] = useState(tags);

const handleClose = (removedTag) => {
const newTags = renderedTags.filter((tag) => tag !== removedTag);
setRenderedTags(newTags);
};

const resetTabs = () => {
setRenderedTags(tags);
};

return (
<>
<Button style={{ marginBottom: '3rem' }} onClick={resetTabs}>
Reset
</Button>
<br />
<div aria-label="Dismissible tags" role="group">
{renderedTags.map((tag, index) => (
<DismissibleTag
key={index}
type={tag.type}
className="some-class"
renderIcon={Asleep}
text={tag.text}
tagTitle={tag.tagTitle}
title="Dismiss"
onClose={(e) => {
e.preventDefault();
handleClose(tag);
}}
{...args}
/>
))}
</div>
</>
);
```

## Selectable

Selectable tags are used to select or unselect single or multiple items.
Expand All @@ -52,6 +110,61 @@ When to use:

<Canvas of={InteractiveTagStories.Selectable} />

Here it is a sample code to help you get started.

```js
const tags = [
{
id: 1,
text: 'Tag content with a long text description',
},
{
id: 2,
text: 'Tag content 1',
},
{
id: 3,
text: 'Tag content 2',
},
{
id: 4,
text: 'Tag content 3',
},
];

const [selectedTags, setSelectedTags] = useState([
{
id: 2,
text: 'Tag content 1',
},
]);

const handleChange = (tag, selected) => {
const nextSelectedTags = selected
? [...selectedTags, tag]
: selectedTags.filter((t) => t.id !== tag.id);

console.log('Selected tags array: ', nextSelectedTags);
setSelectedTags(nextSelectedTags);
};

return (
<div aria-label="Selectable tags" role="group">
{tags.map((tag, index) => (
<SelectableTag
key={index}
renderIcon={Asleep}
text={tag.text}
className="some-class"
selected={selectedTags.find((t) => t.id === tag.id)}
onChange={(selected) => handleChange(tag, selected)}
{...args}
/>
))}
</div>
);
```

## Operational

Operational tags enable the user to view a list of all items associated with a
Expand Down

0 comments on commit 8a75cb5

Please sign in to comment.