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

Tag - Text truncation for overflow fix #2655

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 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/brown-wolves-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashicorp/design-system-components": minor
---

`Tag` - Truncate any text that wraps to multiple lines and add a tooltip with the full text when truncation occurs
dchyun marked this conversation as resolved.
Show resolved Hide resolved
73 changes: 56 additions & 17 deletions packages/components/src/components/hds/tag/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,68 @@
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: MPL-2.0
}}
<Hds::Text::Body class={{this.classNames}} @tag="span" @size="100" @weight="medium" @color="primary" ...attributes>
<Hds::Text::Body
class={{this.classNames}}
@tag="span"
@size="100"
@weight="medium"
@color="primary"
{{this._setUpObserver}}
...attributes
>
{{#if this.onDismiss}}
<button class="hds-tag__dismiss" type="button" aria-label={{this.ariaLabel}} {{on "click" this.onDismiss}}>
<Hds::Icon class="hds-tag__dismiss-icon" @name="x" @size="16" />
</button>
{{/if}}
{{#if (or @href @route)}}
<Hds::Interactive
class="hds-tag__link"
@current-when={{@current-when}}
@models={{hds-link-to-models @model @models}}
@query={{hds-link-to-query @query}}
@replace={{@replace}}
@route={{@route}}
@isRouteExternal={{@isRouteExternal}}
@href={{@href}}
@isHrefExternal={{@isHrefExternal}}
>
{{this.text}}
</Hds::Interactive>
{{#if this._isTextOverflow}}
<Hds::Interactive
class="hds-tag__link"
@current-when={{@current-when}}
@models={{hds-link-to-models @model @models}}
@query={{hds-link-to-query @query}}
@replace={{@replace}}
@route={{@route}}
@isRouteExternal={{@isRouteExternal}}
@href={{@href}}
@isHrefExternal={{@isHrefExternal}}
{{hds-tooltip this.text options=(hash placement="right")}}
>
<div class="hds-tag__text-container">
{{this.text}}
</div>
</Hds::Interactive>
{{else}}
<Hds::Interactive
class="hds-tag__link"
@current-when={{@current-when}}
@models={{hds-link-to-models @model @models}}
@query={{hds-link-to-query @query}}
@replace={{@replace}}
@route={{@route}}
@isRouteExternal={{@isRouteExternal}}
@href={{@href}}
@isHrefExternal={{@isHrefExternal}}
>
<div class="hds-tag__text-container">
{{this.text}}
</div>
</Hds::Interactive>
{{/if}}
{{else}}
<span class="hds-tag__text">
{{this.text}}
</span>
{{#if this._isTextOverflow}}
<Hds::TooltipButton class="hds-tag__text" @text={{this.text}} @placement="right">
<div class="hds-tag__text-container">
{{this.text}}
</div>
</Hds::TooltipButton>
{{else}}
<span class="hds-tag__text">
<div class="hds-tag__text-container">
{{this.text}}
</div>
</span>
{{/if}}
{{/if}}
</Hds::Text::Body>
25 changes: 25 additions & 0 deletions packages/components/src/components/hds/tag/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
*/

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { assert } from '@ember/debug';
import { modifier } from 'ember-modifier';

import { HdsTagColorValues } from './types.ts';
import type { HdsTagColors } from './types.ts';
Expand All @@ -25,6 +27,25 @@ export interface HdsTagSignature {
}

export default class HdsTag extends Component<HdsTagSignature> {
@tracked private _isTextOverflow!: boolean;
private _observer!: ResizeObserver;

private _setUpObserver = modifier((element: HTMLElement) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how this aligns with the AdvancedTable observer!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all credit to dylan, I followed his pattern :)

// Used to detect when text is clipped to one line, and tooltip should be added
this._observer = new ResizeObserver((entries) => {
entries.forEach((entry) => {
this._isTextOverflow = this._isOverflow(
entry.target.querySelector('.hds-tag__text-container')!
);
});
});
this._observer.observe(element);

return () => {
this._observer.disconnect();
};
});

/**
* @param onDismiss
* @type {function}
Expand Down Expand Up @@ -104,4 +125,8 @@ export default class HdsTag extends Component<HdsTagSignature> {

return classes.join(' ');
}

private _isOverflow(el: Element): boolean {
return el.scrollHeight > el.clientHeight;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ™Œ

}
}
22 changes: 22 additions & 0 deletions packages/components/src/styles/components/tag.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ $hds-tag-border-radius: 50px;
.hds-tag {
display: inline-flex;
align-items: stretch;
width: fit-content;
dchyun marked this conversation as resolved.
Show resolved Hide resolved
max-width: 100%;
line-height: 1rem; // 16px - override `body-100`
vertical-align: middle;
background-color: var(--token-color-surface-interactive);
Expand Down Expand Up @@ -45,6 +47,14 @@ $hds-tag-border-radius: 50px;
border-radius: inherit;
}

.hds-tag__text-container {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
line-clamp: 1;
}

.hds-tag__dismiss ~ .hds-tag__text,
.hds-tag__dismiss ~ .hds-tag__link {
padding: 3px 8px 5px 6px;
Expand Down Expand Up @@ -76,6 +86,18 @@ $hds-tag-border-radius: 50px;
}
}

.hds-tooltip-button.hds-tag__text {
dchyun marked this conversation as resolved.
Show resolved Hide resolved
&:focus,
&.mock-focus {
@include hds-focus-ring-basic();
z-index: 1; // ensures focus is not obscured by adjacent elements
}

&:focus-visible::before {
box-shadow: none; // override default tooltip button focus styles
}
}

// COLORS (FOR LINK)

.hds-tag--color-primary {
Expand Down
10 changes: 10 additions & 0 deletions showcase/app/templates/components/tag.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@
<SF.Item {{style width="200px"}}>
<Hds::Tag @text="This is a very long text that should go on multiple lines" />
</SF.Item>
<SF.Item {{style width="200px"}}>
<Hds::Tag
@text="This is a very long text that should go on multiple lines"
@onDismiss={{this.noop}}
@route="components.tag"
/>
</SF.Item>
<SF.Item {{style width="200px"}}>
<Hds::Tag @text="This is a very long text that should go on multiple lines" @route="components.tag" />
</SF.Item>
</Shw::Flex>

<Shw::Divider @level={{2}} />
Expand Down
23 changes: 23 additions & 0 deletions showcase/tests/integration/components/hds/tag/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module('Integration | Component | hds/tag/index', function (hooks) {
await render(hbs`<Hds::Tag @text="My tag" />`);
assert.dom('button.hds-tag__dismiss').doesNotExist();
});

test('it should render the "dismiss" button if a callback function is passed to the @onDismiss argument', async function (assert) {
this.set('NOOP', () => {});
await render(hbs`<Hds::Tag @text="My tag" @onDismiss={{this.NOOP}} />`);
Expand All @@ -44,6 +45,7 @@ module('Integration | Component | hds/tag/index', function (hooks) {
.dom('button.hds-tag__dismiss')
.hasAttribute('aria-label', 'Please dismiss My tag');
});

// COLOR

test('it should render the primary color as the default if no @color prop is declared when the text is a link', async function (assert) {
Expand All @@ -52,12 +54,14 @@ module('Integration | Component | hds/tag/index', function (hooks) {
);
assert.dom('#test-link-tag').hasClass('hds-tag--color-primary');
});

test('it should render the correct CSS color class if the @color prop is declared when the text is a link', async function (assert) {
await render(
hbs`<Hds::Tag @text="My text tag" @href="/" @color="secondary" id="test-link-tag"/>`
);
assert.dom('#test-link-tag').hasClass('hds-tag--color-secondary');
});

test('it should throw an assertion if an incorrect value for @color is provided when the text is a link', async function (assert) {
const errorMessage =
'@color for "Hds::Tag" must be one of the following: primary, secondary; received: foo';
Expand All @@ -70,6 +74,7 @@ module('Integration | Component | hds/tag/index', function (hooks) {
throw new Error(errorMessage);
});
});

test('it should throw an assertion if @color is provided without @href or @route', async function (assert) {
const errorMessage =
'@color can only be applied to "Hds::Tag" along with either @href or @route';
Expand All @@ -82,4 +87,22 @@ module('Integration | Component | hds/tag/index', function (hooks) {
throw new Error(errorMessage);
});
});

// OVERFLOW

test('it should not render a tooltip if the text does not overflow', async function (assert) {
await render(hbs`
<Hds::Tag @text="My text tag" id="test-tag"/>
`);
assert.dom('.hds-tooltip-button').doesNotExist();
});

test('it should render a tooltip if the text overflows', async function (assert) {
await render(hbs`
<div style="width: 50px;">
dchyun marked this conversation as resolved.
Show resolved Hide resolved
<Hds::Tag @text="This is a very long text that should go on multiple lines" id="test-tag"/>
</div>
`);
assert.dom('.hds-tooltip-button').exists();
});
});