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

feat(TagOverflow): overflow tooltip variants #5249

Merged
merged 12 commits into from
May 28, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ $block-class-modal: #{$block-class}-modal;
max-width: $spacing-09;
}

.#{c4p-settings.$carbon-prefix}--popover
.#{c4p-settings.$carbon-prefix}--popover-content {
white-space: normal;
}

.#{$block-class-overflow} {
display: inline-block;
vertical-align: bottom;
Expand Down Expand Up @@ -139,8 +144,6 @@ $block-class-modal: #{$block-class}-modal;
margin: 0;
background-color: inherit;
color: inherit;
text-overflow: ellipsis;
white-space: nowrap;
}

.#{$block-class-overflow}__tag
Expand Down
33 changes: 21 additions & 12 deletions packages/ibm-products/src/components/TagOverflow/TagOverflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ const componentName = 'TagOverflow';

const allTagsModalSearchThreshold = 10;

// TODO: support prop overflowType

// Default values for props
const defaults = {
items: [],
Expand Down Expand Up @@ -182,8 +180,8 @@ export let TagOverflow = React.forwardRef(
}

const hiddenItems = items?.slice(visibleItemsArr.length);
const overflowItemsArr = hiddenItems?.map((item) => {
return { type: item.tagType, label: item.label, id: item.id };
const overflowItemsArr = hiddenItems?.map(({ tagType, ...other }) => {
return { type: tagType, ...other };
});

setVisibleItems(visibleItemsArr);
Expand All @@ -197,6 +195,16 @@ export let TagOverflow = React.forwardRef(
onOverflowTagChange,
]);

const handleTagOnClose = useCallback(
(onClose, index) => {
onClose?.();
if (index <= visibleItems.length - 1) {
setPopoverOpen(false);
}
},
[visibleItems]
);

return (
<div
{
Expand All @@ -211,23 +219,23 @@ export let TagOverflow = React.forwardRef(
{...getDevtoolsProps(componentName)}
>
{visibleItems.length > 0 &&
visibleItems.map((item) => {
visibleItems.map((item, index) => {
// Render custom components
if (tagComponent) {
return getCustomComponent(item);
} else {
const { id, label, tagType, onClose, ...other } = item;
// If there is no template prop, then render items as Tags
return (
<div
ref={(node) => itemRefHandler(item.id, node)}
key={item.id}
>
<Tooltip align="bottom" label={item.label}>
<div ref={(node) => itemRefHandler(id, node)} key={id}>
<Tooltip align={overflowAlign} label={label}>
<Tag
{...other}
className={`${blockClass}__item--tag`}
type={item.tagType}
type={tagType}
onClose={() => handleTagOnClose(onClose, index)}
>
{item.label}
{label}
</Tag>
</Tooltip>
</div>
Expand Down Expand Up @@ -256,6 +264,7 @@ export let TagOverflow = React.forwardRef(
open={showAllModalOpen}
title={allTagsModalTitle}
onClose={handleModalClose}
overflowType={overflowType}
searchLabel={allTagsModalSearchLabel}
searchPlaceholder={allTagsModalSearchPlaceholderText}
portalTarget={allTagsModalTarget}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import React, { useState, useRef } from 'react';
import { Theme } from '@carbon/react';

import { pkg } from '../../settings';
Expand Down Expand Up @@ -120,3 +120,40 @@ CustomComponentsWithOverflowModal.args = {
tagComponent: IconComponent,
...overflowAndModalStrings,
};

const TemplateWithClose = (argsIn) => {
const { containerWidth, allTagsModalTargetCustomDomNode, items, ...args } = {
...argsIn,
};
const [liveTags, setLiveTags] = useState(
items.map((item) => ({
...item,
filter: true,
onClose: () => handleTagClose(item.label),
}))
);

const handleTagClose = (key) => {
setLiveTags((prev) => prev.filter((item) => item.label !== key));
};

const ref = useRef();
return (
<div style={{ width: containerWidth }} ref={ref}>
<TagOverflow
{...args}
items={liveTags}
allTagsModalTarget={
allTagsModalTargetCustomDomNode ? ref.current : undefined
}
/>
</div>
);
};

export const InteractiveTags = TemplateWithClose.bind({});
InteractiveTags.args = {
items: tags,
containerWidth: 500,
...overflowAndModalStrings,
};
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const TagOverflowModal = ({
title,
onClose,
open,
overflowType,
portalTarget: portalTargetIn,
searchLabel = defaults.searchLabel,
searchPlaceholder,
Expand All @@ -49,28 +50,12 @@ export const TagOverflowModal = ({
const renderPortalUse = usePortalTarget(portalTargetIn);

const getFilteredItems = () => {
let newFilteredModalTags = [];
if (open) {
if (search === '') {
newFilteredModalTags = allTags.slice(0);
} else {
const lCaseSearch = search.toLocaleLowerCase();

allTags.forEach((tag) => {
const dataSearch = tag['data-search']
?.toLocaleLowerCase()
?.indexOf(lCaseSearch);
const labelSearch = tag.label
?.toLocaleLowerCase()
?.indexOf(lCaseSearch);

if (dataSearch > -1 || labelSearch > -1) {
newFilteredModalTags.push(tag);
}
});
}
if (open && search) {
return allTags.filter((tag) =>
tag.label?.toLocaleLowerCase()?.includes(search.toLocaleLowerCase())
);
}
return newFilteredModalTags;
return allTags;
};

const handleSearch = (evt) => {
Expand Down Expand Up @@ -104,11 +89,14 @@ export const TagOverflowModal = ({
/>
</ModalHeader>
<ModalBody className={`${blockClass}__body`} hasForm>
{getFilteredItems().map(({ label, id, ...other }) => (
<Tag {...other} filter={false} key={id}>
{label}
</Tag>
))}
{getFilteredItems().map(({ label, id, filter, ...other }) => {
const isFilterable = overflowType === 'tag' ? filter : false;
return (
<Tag {...other} key={id} filter={isFilterable}>
{label}
</Tag>
);
})}
</ModalBody>
<div className={`${blockClass}__fade`} />
</ComposedModal>
Expand All @@ -125,6 +113,7 @@ TagOverflowModal.propTypes = {
className: PropTypes.string,
onClose: PropTypes.func,
open: PropTypes.bool,
overflowType: PropTypes.oneOf(['default', 'tag']),
portalTarget: PropTypes.node,
searchLabel: PropTypes.string,
searchPlaceholder: PropTypes.string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,29 +102,39 @@ export const TagOverflowPopover = React.forwardRef(
<PopoverContent>
<div ref={overflowTagContent} className={`${blockClass}__content`}>
<ul className={`${blockClass}__tag-list`}>
{getOverflowPopoverItems().map((tag) => {
const tagProps = {};
if (overflowType === 'tag') {
tagProps.type = 'high-contrast';
}
if (overflowType === 'default') {
tagProps.filter = false;
{getOverflowPopoverItems().map(
({ label, id, tagType, filter, onClose, ...other }) => {
const typeValue =
overflowType === 'tag' ? 'high-contrast' : tagType;
const isFilterable =
overflowType === 'tag' ? filter : false;

return (
<li
className={cx(`${blockClass}__tag-item`, {
[`${blockClass}__tag-item--default`]:
overflowType === 'default',
[`${blockClass}__tag-item--tag`]:
overflowType === 'tag',
})}
key={id}
>
{overflowType === 'tag' ? (
<Tag
{...other}
onClose={() => onClose?.()}
type={typeValue}
filter={isFilterable}
>
{label}
</Tag>
) : (
label
)}
</li>
);
}
return (
<li
className={cx(`${blockClass}__tag-item`, {
[`${blockClass}__tag-item--default`]:
overflowType === 'default',
[`${blockClass}__tag-item--tag`]:
overflowType === 'tag',
})}
key={tag.id}
>
{tag.label}
{/* {React.cloneElement(tag, tagProps)} */}
</li>
);
})}
)}
</ul>
{overflowTags.length > allTagsModalSearchThreshold && (
<Link
Expand Down
Loading