diff --git a/cspell.json b/cspell.json index a74c4c07e4a..0f14786b776 100644 --- a/cspell.json +++ b/cspell.json @@ -1,11 +1,7 @@ { "version": "0.1", "language": "en", - "dictionaries": [ - "contributors", - "html", - "packages" - ], + "dictionaries": ["contributors", "html", "packages"], "dictionaryDefinitions": [ { "name": "contributors", @@ -207,6 +203,7 @@ "webfonts", "useravatar", "rendericon", - "csvg" + "csvg", + "autoalign" ] } diff --git a/packages/ibm-products-web-components/.storybook/_container.scss b/packages/ibm-products-web-components/.storybook/_container.scss index 34b2db0b13f..f09b1bcfee3 100644 --- a/packages/ibm-products-web-components/.storybook/_container.scss +++ b/packages/ibm-products-web-components/.storybook/_container.scss @@ -7,7 +7,9 @@ // LICENSE file in the root directory of this source tree. // -@use '@carbon/styles'; +@use '@carbon/styles' with ( + $font-path: '@ibm/plex' +); @use '@carbon/styles/scss/config' as *; @use '@carbon/themes/scss/themes'; @use '@carbon/styles/scss/theme'; @@ -60,6 +62,10 @@ body { padding: 0; } +.sbdocs-preview { + background-color: theme.$background; +} + #main-content { position: relative; /* stylelint-disable-next-line carbon/layout-use */ diff --git a/packages/ibm-products-web-components/examples/truncated-text/.gitignore b/packages/ibm-products-web-components/examples/truncated-text/.gitignore new file mode 100644 index 00000000000..a547bf36d8d --- /dev/null +++ b/packages/ibm-products-web-components/examples/truncated-text/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/ibm-products-web-components/examples/truncated-text/.sassrc b/packages/ibm-products-web-components/examples/truncated-text/.sassrc new file mode 100644 index 00000000000..956b9e0a3d8 --- /dev/null +++ b/packages/ibm-products-web-components/examples/truncated-text/.sassrc @@ -0,0 +1,6 @@ +{ + "includePaths": [ + "node_modules", + "../../node_modules" + ] +} \ No newline at end of file diff --git a/packages/ibm-products-web-components/examples/truncated-text/gallery.config.json b/packages/ibm-products-web-components/examples/truncated-text/gallery.config.json new file mode 100644 index 00000000000..94e9a8979a4 --- /dev/null +++ b/packages/ibm-products-web-components/examples/truncated-text/gallery.config.json @@ -0,0 +1,4 @@ +{ + "label": "Truncated Text", + "template": "node" +} diff --git a/packages/ibm-products-web-components/examples/truncated-text/index.html b/packages/ibm-products-web-components/examples/truncated-text/index.html new file mode 100644 index 00000000000..db5e4abb2c1 --- /dev/null +++ b/packages/ibm-products-web-components/examples/truncated-text/index.html @@ -0,0 +1,31 @@ + + + + + carbon-web-components example + + + + + +
+
+
+

+ +

+
+
+ + diff --git a/packages/ibm-products-web-components/examples/truncated-text/package.json b/packages/ibm-products-web-components/examples/truncated-text/package.json new file mode 100644 index 00000000000..f2ce6611ce5 --- /dev/null +++ b/packages/ibm-products-web-components/examples/truncated-text/package.json @@ -0,0 +1,23 @@ +{ + "name": "truncated-text", + "version": "0.1.0", + "private": true, + "description": "Sample project for getting started with the Web Components from Carbon for IBM Products.", + "license": "Apache-2", + "main": "index.html", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "clean": "rimraf node_modules dist .cache" + }, + "dependencies": { + "@carbon/ibm-products-web-components": "^0.10.0-rc.0", + "lit": "^3.2.1", + "sass": "^1.64.1" + }, + "devDependencies": { + "rimraf": "^3.0.2", + "vite": "5.4.18" + } +} diff --git a/packages/ibm-products-web-components/examples/truncated-text/src/index.js b/packages/ibm-products-web-components/examples/truncated-text/src/index.js new file mode 100644 index 00000000000..20389005020 --- /dev/null +++ b/packages/ibm-products-web-components/examples/truncated-text/src/index.js @@ -0,0 +1,10 @@ +/** + * @license + * + * Copyright IBM Corp. 2025 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import '@carbon/ibm-products-web-components/es/components/truncated-text/index.js'; diff --git a/packages/ibm-products-web-components/examples/truncated-text/src/styles.scss b/packages/ibm-products-web-components/examples/truncated-text/src/styles.scss new file mode 100644 index 00000000000..3a0b89b5358 --- /dev/null +++ b/packages/ibm-products-web-components/examples/truncated-text/src/styles.scss @@ -0,0 +1,18 @@ +/** + * @license + * + * Copyright IBM Corp. 2025 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +@use '@carbon/styles/scss/reset'; +@use '@carbon/styles/scss/theme'; +@use '@carbon/styles/scss/themes'; + +:root { + @include theme.theme(themes.$white); + background-color: $background; + color: $text-primary; +} diff --git a/packages/ibm-products-web-components/examples/truncated-text/vite.config.js b/packages/ibm-products-web-components/examples/truncated-text/vite.config.js new file mode 100644 index 00000000000..bdd7811a68f --- /dev/null +++ b/packages/ibm-products-web-components/examples/truncated-text/vite.config.js @@ -0,0 +1,21 @@ +/** + * @license + * + * Copyright IBM Corp. 2025 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { resolve } from 'path'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + build: { + rollupOptions: { + input: { + main: resolve(__dirname, 'index.html'), + }, + }, + }, +}); diff --git a/packages/ibm-products-web-components/src/components/truncated-text/index.ts b/packages/ibm-products-web-components/src/components/truncated-text/index.ts new file mode 100644 index 00000000000..944e80fab3c --- /dev/null +++ b/packages/ibm-products-web-components/src/components/truncated-text/index.ts @@ -0,0 +1,10 @@ +/** + * @license + * + * Copyright IBM Corp. 2025, 2025 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import './truncated-text'; diff --git a/packages/ibm-products-web-components/src/components/truncated-text/story-styles.scss b/packages/ibm-products-web-components/src/components/truncated-text/story-styles.scss new file mode 100644 index 00000000000..230d806809b --- /dev/null +++ b/packages/ibm-products-web-components/src/components/truncated-text/story-styles.scss @@ -0,0 +1,13 @@ +/* +* Copyright IBM Corp. 2025, 2025 +* +* This source code is licensed under the Apache-2.0 license found in the +* LICENSE file in the root directory of this source tree. +*/ + +@use '@carbon/styles/scss/theme' as *; + +// $story-prefix: 'truncated-text-stories__'; +#main-content { + padding: 12rem; +} diff --git a/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.mdx b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.mdx new file mode 100644 index 00000000000..acde72a34c4 --- /dev/null +++ b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.mdx @@ -0,0 +1,56 @@ +import { ArgTypes, Markdown, Meta } from '@storybook/blocks'; +import { Canvas } from '@storybook/addon-docs'; +import { cdnJs, cdnCss } from '../../globals/internal/storybook-cdn'; +import * as TruncatedText from './truncated-text.stories'; + + + +# TruncatedText + +The truncated text utility can truncate text based on a specified number of +lines, provided via a prop. It offers two configurable options for revealing the +full content: + +- Tooltip mode `with="tooltip"`: Displays the full text in a tooltip overlay on + hover. + +- Expandable mode `with="expand"`: Reveals the complete content through + collapsible “Read more” / “Read less” toggles. These labels can be customized + using the `expand-label` and `collapse-label` attributes on the component. + +> 💡 Check our +> [Stackblitz](https://stackblitz.com/github/carbon-design-system/ibm-products/tree/main/packages/ibm-products-web-components/examples/truncated-text) +> example implementation. +> [![Edit carbon-web-components](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/carbon-design-system/ibm-products/tree/main/packages/ibm-products-web-components/examples/truncated-text) + +## Default with tooltip + + + +## With expand + + + +## Getting started + +Here's a quick example to get you started. + +### JS (via import) + +```javascript +import '@carbon/ibm-products-web-components/es/components/truncated-text/index.js'; +``` + +### HTML + +```html + +``` + +## `` attributes, properties and events + + diff --git a/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.scss b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.scss new file mode 100644 index 00000000000..9380ff6b71e --- /dev/null +++ b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.scss @@ -0,0 +1,122 @@ +/* +* Copyright IBM Corp. 2025, 2025 +* +* This source code is licensed under the Apache-2.0 license found in the +* LICENSE file in the root directory of this source tree. +*/ + +// $css--plex: true !default; + +@use 'sass:map'; +@use '@carbon/styles/scss/utilities' as *; +@use '@carbon/themes' as *; +@use '@carbon/styles/scss/spacing' as *; +@use '@carbon/styles/scss/theme' as *; +@use '@carbon/styles/scss/motion' as *; +@use '@carbon/styles/scss/config'; + +$prefix: 'c4p'; +$carbon-prefix: config.$prefix; +$block-class: #{$prefix}--truncated-text; + +// Common reset button style +%reset-button { + border: none; + cursor: pointer; + font: inherit; + line-height: inherit; +} + +// Mixin for directional gradients +@mixin gradient-bg($direction, $color) { + background: linear-gradient($direction, rgba(255, 255, 255, 0) 0%, $color 0%); +} + +:host(#{$prefix}-truncated-text) { + display: block; + inline-size: 100%; + + .#{$block-class}_transition { + @media (prefers-reduced-motion: no-preference) { + transition: max-height $duration-fast-02 motion(standard, productive); + } + } + + .#{$block-class}_content { + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + -webkit-line-clamp: var(--line-clamp, 0); + text-overflow: clip; + white-space: normal; + + &:focus { + @include focus-outline('outline'); + } + } + + #{$carbon-prefix}-tooltip { + display: inline-flex; + } + + button:focus, + .#{$block-class}_button-expand:focus, + .#{$block-class}_button-collapse:focus { + @include focus-outline('outline'); + } +} + +.#{$block-class}_button-expand, +.#{$block-class}_button-collapse { + @extend %reset-button; + + padding: 0 $spacing-01; + color: $link-primary; + + &.#{$block-class}_button-hide { + display: none; + } +} + +.#{$block-class}_button-expand { + position: absolute; + background-color: $background; + inset-block-end: 0; + inset-inline-end: 0; + + &.#{$block-class}_button-layered { + background-color: $layer; + } + + &::before { + position: absolute; + block-size: 100%; + color: $text-primary; + content: '...'; + inline-size: 1em; // use em so truncation element scales with parent font size + inset-inline-start: -1em; // positions pseudo element before button without overlap across font sizes + pointer-events: none; + } + + // Gradient LTR + &:dir(ltr)::before { + @include gradient-bg(90deg, $background); + } + + &.#{$block-class}_button-layered:dir(ltr)::before { + @include gradient-bg(90deg, $layer); + } + + // Gradient RTL + &:dir(rtl)::before { + @include gradient-bg(270deg, $background); + } + + &.#{$block-class}_button-layered:dir(rtl)::before { + @include gradient-bg(270deg, $layer); + } +} + +.#{$block-class}_button-collapse { + background-color: transparent; +} diff --git a/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.stories.ts b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.stories.ts new file mode 100644 index 00000000000..ca813bd975a --- /dev/null +++ b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.stories.ts @@ -0,0 +1,322 @@ +/** + * @license + * + * Copyright IBM Corp. 2025, 2025 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { html } from 'lit'; +import './index'; +import mdx from './truncated-text.mdx'; +import CDSTruncatedText from './truncated-text'; +import styles from './story-styles.scss?lit'; +import '@carbon/web-components/es/components/layer/layer.js'; + +// const storyPrefix = 'truncated-text-stories__'; +const defaultArgs = { + lines: 2, + value: + 'Buttons are used to initialize an action, either in the background or foreground of an experience. There are several kinds of buttons. Primary buttons should be used for the principal call to action on the page. Secondary buttons should be used for secondary actions on each page. Danger buttons should be used for a negative action (such as Delete) on the page.', + element: 'p', + align: 'bottom', + with: 'tooltip', + autoalign: false, +}; + +const argTypes = { + align: { + control: { + type: 'select', + }, + options: [ + 'top', + 'bottom', + 'left', + 'right', + 'left-bottom', + 'left-top', + 'right-bottom', + 'right-top', + ], + }, + autoalign: { + control: { + type: 'boolean', + }, + }, + lines: { + control: { + type: 'number', + }, + }, + value: { + control: { + type: 'text', + }, + }, + with: { + control: { type: 'select' }, + options: ['tooltip', 'expand'], + }, + expandLabel: { + table: { + disable: true, + }, + control: { + type: 'text', + }, + }, + collapseLabel: { + table: { + disable: true, + }, + control: { + type: 'text', + }, + }, + element: { + description: + 'This is a story-only control to show the Truncated Text in different HTML elements, which just sets the parent element of the Truncated Text.', + control: { + type: 'select', + labels: { + p: 'Paragraph', + layers: 'With layers', + h1: 'Heading 1', + h2: 'Heading 2', + h3: 'Heading 3', + h4: 'Heading 4', + h5: 'Heading 5', + h6: 'Heading 6', + }, + }, + options: ['p', 'layers', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'], + }, +}; + +const renderTemplate = (args) => { + const { lines, value, with: withMode, align, autoalign } = args; + const expandLabelAttr = args['expand-label']; + const collapseLabelAttr = args['collapse-label']; + return html` + + ${args.element === 'p' + ? html`

+ +

` + : ''} + ${args.element === 'layers' + ? html` +

+ +

+ +
+

+ +

+
+ +
+

+ +

+
+ +
+

+ +

+
+
+
+
+ ` + : ''} + ${args.element === 'h1' + ? html`

+ +

` + : ''} + ${args.element === 'h2' + ? html`

+ +

` + : ''} + ${args.element === 'h3' + ? html`

+ +

` + : ''} + ${args.element === 'h4' + ? html`

+ +

` + : ''} + ${args.element === 'h5' + ? html`
+ +
` + : ''} + ${args.element === 'h6' + ? html`
+ +
` + : ''} + `; +}; + +export const Default = { + args: { + ...defaultArgs, + }, + argTypes, + name: 'Default with Tooltip', + render: renderTemplate, + parameters: { + docs: { + source: { + code: ` + + `, + }, + }, + }, +}; + +export const WithExpand = { + args: { + ...defaultArgs, + with: 'expand', + }, + argTypes, + render: renderTemplate, + parameters: { + docs: { + source: { + code: ` + + `, + }, + }, + }, +}; + +const meta = { + title: 'Experimental/Utilities/TruncatedText', + component: 'c4p-truncated-text', + parameters: { + layout: 'fullscreen', + }, +}; + +export default meta; diff --git a/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.test.ts b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.test.ts new file mode 100644 index 00000000000..b39a32a7a88 --- /dev/null +++ b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.test.ts @@ -0,0 +1,95 @@ +/** + * Copyright IBM Corp. 2025, 2025 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { describe, it, expect } from 'vitest'; +import { fixture, html } from '@open-wc/testing'; +import CDSTruncatedText from './truncated-text'; +import { prefix, carbonPrefix } from '../../globals/settings'; +import './index'; + +const defaultProps = { + lines: 2, + text: 'Buttons are used to initialize an action, either in the background or foreground of an experience. There are several kinds of buttons. Primary buttons should be used for the principle call to action on the page.', + with: '', +}; + +const template = (props = defaultProps, templateWidth?: number) => html` +
+ +
+`; + +describe('c4p-truncated-text', () => { + it('renders the component', async () => { + const wrapper = await fixture(template()); + const el = wrapper.querySelector(`${prefix}-truncated-text`); + expect(el).toBeTruthy(); + }); + + it('renders a tooltip when text is truncated with tooltip', async () => { + const wrapper = await fixture( + template({ ...defaultProps, with: 'tooltip' }, 200) + ); + + const el = wrapper.querySelector( + `${prefix}-truncated-text` + ) as CDSTruncatedText; + const tooltip = el.shadowRoot?.querySelector(`${carbonPrefix}-tooltip`); + expect(tooltip).toBeTruthy(); + }); + + it('does not render a tooltip if the text fits', async () => { + const wrapper = await fixture( + template({ ...defaultProps, with: 'tooltip' }, 9000) + ); + + const el = wrapper.querySelector( + `${prefix}-truncated-text` + ) as CDSTruncatedText; + + const tooltip = el.shadowRoot?.querySelector(`${carbonPrefix}-tooltip`); + expect(tooltip).not.toBeTruthy(); + }); + + it('tests lines prop/attribute with tooltip', async () => { + for (let lines = 1; lines <= 4; lines++) { + const wrapper = await fixture( + template({ ...defaultProps, lines, with: 'tooltip' }, 600) + ); + + const el = wrapper.querySelector( + `${prefix}-truncated-text` + ) as CDSTruncatedText; + await el.updateComplete; + + const tooltip = el.shadowRoot?.querySelector(`${carbonPrefix}-tooltip`); + if (lines <= 2) { + expect(tooltip).toBeTruthy(); + } else { + expect(tooltip).not.toBeTruthy(); + } + } + }); + + it('renders a expandable button when text is truncated with expand', async () => { + const wrapper = await fixture( + template({ ...defaultProps, lines: 2, with: 'expand' }, 400) + ); + const el = wrapper.querySelector( + `${prefix}-truncated-text` + ) as CDSTruncatedText; + await el.updateComplete; + const expandButton = el.shadowRoot?.querySelector( + `.${prefix}--truncated-text_button-expand` + ); + expect(expandButton).toBeTruthy(); + }); +}); diff --git a/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.ts b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.ts new file mode 100644 index 00000000000..bb3496a9316 --- /dev/null +++ b/packages/ibm-products-web-components/src/components/truncated-text/truncated-text.ts @@ -0,0 +1,232 @@ +/** + * @license + * + * Copyright IBM Corp. 2025, 2025 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ +import { html, LitElement } from 'lit'; +import { property, query, state } from 'lit/decorators.js'; +import { classMap } from 'lit/directives/class-map.js'; +import { carbonElement as customElement } from '@carbon/web-components/es/globals/decorators/carbon-element.js'; +import { prefix, carbonPrefix } from '../../globals/settings'; +import '@carbon/web-components/es/components/tooltip/index.js'; +import '@carbon/web-components/es/components/button/button.js'; +import '@carbon/web-components/es/components/link/index.js'; + +import styles from './truncated-text.scss?lit'; + +const componentName = 'truncated-text'; +export const blockClass = `${prefix}--${componentName}`; +const elementName = `${prefix}-${componentName}`; // c4p-truncated-text + +/** + * TruncatedText. + * + * @element c4p-truncated-text + */ +@customElement(elementName) +export class CDSTruncatedText extends LitElement { + /** + * The maximum number of lines to display before truncation. + */ + @property({ type: Number, reflect: true }) lines = 0; + + /** + * The string value to be truncated. + */ + @property({ type: String, attribute: 'value', reflect: true }) value = ''; + + /** + * Specify how the tooltip should align with the content. + */ + @property({ reflect: true, type: String }) + align = 'top'; + + /** + * Specify whether a auto align functionality should be applied + */ + @property({ type: Boolean, reflect: true }) + autoalign = false; + + /** + * The label on expand button. + */ + @property({ attribute: 'expand-label', type: String, reflect: true }) + expandLabel = 'View more'; + + /** + * The label on the collapse button. + */ + @property({ attribute: 'collapse-label', type: String, reflect: true }) + collapseLabel = 'View less'; + + /** + * The method to display the full text when truncated. Options are "tooltip" or "expand". if not passed, the text would just be truncated with ellipsis. + */ + @property({ type: String, reflect: true }) with: 'tooltip' | 'expand' = + 'tooltip'; + + @state() private _isOverflowing: boolean = false; + @state() private _isExpanded: boolean = false; + @state() private _maxHeight: string = 'none'; + + @query(`.${blockClass}_content`) private _textElement!: HTMLElement; + private _lineHeight: number = 0; + private _isLayered: boolean = false; + private _resizeObserver?: ResizeObserver; + + static styles = styles; + + connectedCallback() { + super.connectedCallback(); + this._isLayered = !!this.closest(`${carbonPrefix}-layer`); + this.with = this.with || 'tooltip'; + } + + disconnectedCallback() { + this._resizeObserver?.disconnect(); + super.disconnectedCallback(); + } + + protected firstUpdated() { + requestAnimationFrame(() => { + const computedStyle = getComputedStyle(this._textElement); + this._lineHeight = parseFloat(computedStyle.lineHeight); + this._setupResizeObserver(); + }); + } + + protected updated(changed: Map) { + if (changed.has('lines') || changed.has('value')) { + this._updateOverflowStatus(); + this._updateMaxHeight(); + } + } + + private _updateMaxHeight() { + if (this.with !== 'expand') { + return; + } + requestAnimationFrame(() => { + if (!this._textElement) { + return; + } + this._maxHeight = + this.lines > 0 && !this._isExpanded + ? `${this.lines * this._lineHeight}px` + : `${this._textElement.scrollHeight}px`; + }); + } + + private _setupResizeObserver() { + if (!this._textElement) { + return; + } + + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + } + + this._resizeObserver = new ResizeObserver(() => { + this._updateOverflowStatus(); + }); + + this._resizeObserver.observe(this); + } + + private _updateOverflowStatus() { + if (!this._textElement || this.lines <= 0) { + return; + } + this._updateMaxHeight(); + const { scrollHeight, clientHeight } = this._textElement; + const buffer = this._lineHeight / 2; // buffer of at least half of line height for a stable outcome + + const isOverflowing = scrollHeight > clientHeight + buffer; + + if (isOverflowing !== this._isOverflowing) { + this._isOverflowing = isOverflowing; + } + } + + private _toggleExpansion() { + this._isExpanded = !this._isExpanded; + this._updateMaxHeight(); + this._textElement?.classList.add(`${blockClass}_transition`); + const onTransitionEnd = () => { + this._textElement?.querySelector('button')?.focus(); + this._textElement?.removeEventListener('transitionend', onTransitionEnd); + }; + this._textElement?.addEventListener('transitionend', onTransitionEnd); + } + + private _renderToggleButton() { + if (this.with !== 'expand') { + return; + } + const className = classMap({ + [`${blockClass}_button-collapse`]: this._isExpanded, + [`${blockClass}_button-expand`]: !this._isExpanded, + [`${blockClass}_button-layered`]: this._isLayered, + [`${blockClass}_button-hide`]: !this._isOverflowing && !this._isExpanded, + }); + const label = this._isExpanded + ? this.collapseLabel || 'View less' + : this.expandLabel || 'View more'; + return html` + + `; + } + + render() { + // Apply different styles based on truncation method + const contentStyle = + this.with === 'tooltip' || !this.with + ? `--line-clamp: ${this._isExpanded ? 'none' : this.lines};` + : `max-block-size: ${this._maxHeight};`; + + const contentClass = classMap({ + [`${blockClass}_content`]: true, + }); + + const content = html` +
+ ${this.value} ${this._renderToggleButton()} +
+ `; + + return this.with === 'tooltip' && this._isOverflowing + ? html` + + ${content} + ${this.value} + + ` + : html`
+ ${content} +
`; + } +} + +declare global { + interface HTMLElementTagNameMap { + [elementName]: CDSTruncatedText; + } +} + +export default CDSTruncatedText; diff --git a/packages/ibm-products-web-components/src/index.ts b/packages/ibm-products-web-components/src/index.ts index 3528ded1075..efbcef15a32 100644 --- a/packages/ibm-products-web-components/src/index.ts +++ b/packages/ibm-products-web-components/src/index.ts @@ -13,3 +13,4 @@ export { default as CDSFullPageError } from './components/full-page-error/full-p export { default as CDSAboutModal } from './components/about-modal/about-modal'; export { default as CDSUseravatar } from './components/user-avatar/user-avatar'; export { default as CDSOptionsTile } from './components/options-tile/options-tile'; +export { default as CDSTruncatedText } from './components/truncated-text/truncated-text';