diff --git a/packages/core/src/components/popover-canvas/popover-canvas.stories.tsx b/packages/core/src/components/popover-canvas/popover-canvas.stories.tsx index eb89dcce0..a525d2683 100644 --- a/packages/core/src/components/popover-canvas/popover-canvas.stories.tsx +++ b/packages/core/src/components/popover-canvas/popover-canvas.stories.tsx @@ -34,13 +34,25 @@ export default { defaultValue: { summary: 'auto' }, }, }, + animation: { + name: 'Animation', + description: 'Sets the animation of the Popover Canvas.', + control: { + type: 'radio', + }, + options: ['none', 'fade'], + table: { + defaultValue: { summary: 'none' }, + }, + }, }, args: { canvasPosition: 'Auto', + animation: 'none', }, }; -const ComponentPopoverCanvas = ({ canvasPosition }) => { +const ComponentPopoverCanvas = ({ canvasPosition, animation }) => { const canvasPosLookup = { 'Bottom': 'bottom', 'Bottom start': 'bottom-start', @@ -78,6 +90,7 @@ const ComponentPopoverCanvas = ({ canvasPosition }) => {

A Popover Canvas!

diff --git a/packages/core/src/components/popover-canvas/popover-canvas.tsx b/packages/core/src/components/popover-canvas/popover-canvas.tsx index 2cb4a33bd..f6aaf45c7 100644 --- a/packages/core/src/components/popover-canvas/popover-canvas.tsx +++ b/packages/core/src/components/popover-canvas/popover-canvas.tsx @@ -36,6 +36,9 @@ export class TdsPopoverCanvas { /** Sets the offset skidding */ @Prop() offsetSkidding: number = 0; + /** Whether the popover should animate when being opened/closed or not */ + @Prop() animation: 'none' | 'fade' | string = 'none'; + /** Sets the offset distance */ @Prop() offsetDistance: number = 8; @@ -76,6 +79,7 @@ export class TdsPopoverCanvas { this.childRef = el; }} defaultShow={this.defaultShow} + animation={this.animation} >
{/* (@stencil/core@3.3.0): This div is somehow needed to keep the slotted children in a predictable order */} diff --git a/packages/core/src/components/popover-canvas/readme.md b/packages/core/src/components/popover-canvas/readme.md index 3f5563e31..42dfc0e17 100644 --- a/packages/core/src/components/popover-canvas/readme.md +++ b/packages/core/src/components/popover-canvas/readme.md @@ -64,6 +64,7 @@ Example: | Property | Attribute | Description | Type | Default | | ---------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | +| `animation` | `animation` | Whether the popover should animate when being opened/closed or not | `string` | `'none'` | | `defaultShow` | `default-show` | Decides if the component should be visible from the start. | `boolean` | `false` | | `modifiers` | -- | Array of modifier objects to pass to popper.js. See https://popper.js.org/docs/v2/modifiers/ | `Object[]` | `[]` | | `offsetDistance` | `offset-distance` | Sets the offset distance | `number` | `8` | diff --git a/packages/core/src/components/popover-core/popover-core.tsx b/packages/core/src/components/popover-core/popover-core.tsx index 3d4ee87cc..15d4bed51 100644 --- a/packages/core/src/components/popover-core/popover-core.tsx +++ b/packages/core/src/components/popover-core/popover-core.tsx @@ -14,9 +14,11 @@ import { import { createPopper } from '@popperjs/core'; import type { Placement, Instance } from '@popperjs/core'; import generateUniqueId from '../../utils/generateUniqueId'; +import { generateClassList } from '../../utils/classList'; @Component({ tag: 'tds-popover-core', + styleUrl: 'tds-popover-core.scss', shadow: false, scoped: true, }) @@ -32,6 +34,9 @@ export class TdsPopoverCore { /** Decides if the component should be visible from the start. */ @Prop() defaultShow: boolean = false; + /** Whether the popover should animate when being opened/closed or not */ + @Prop() animation: 'none' | 'fade' | string = 'none'; + /** Controls whether the Popover is shown or not. If this is set hiding and showing * will be decided by this prop and will need to be controlled from the outside. This * also means that clicking outside of the popover won't close it. Takes precedence over `defaultShow` prop. @@ -67,6 +72,8 @@ export class TdsPopoverCore { @State() disableLogic: boolean = false; + @State() hasShownAtLeastOnce: boolean = false; + /** Property for closing popover programmatically */ @Method() async close() { this.setIsShown(false); @@ -141,6 +148,7 @@ export class TdsPopoverCore { this.isShown = isShown; } if (this.isShown) { + this.hasShownAtLeastOnce = true; this.internalTdsShow.emit(); } else { this.internalTdsClose.emit(); @@ -245,10 +253,13 @@ export class TdsPopoverCore { }); } - /* To enable initial loading of a component if user controls show prop*/ + /* To enable initial loading of a component if user controls show prop */ componentWillLoad() { + // Ensure initial visibility is handled properly if (this.show === true || this.defaultShow === true) { this.setIsShown(true); + } else { + this.setIsShown(false); } } @@ -266,13 +277,18 @@ export class TdsPopoverCore { } render() { - let hostStyle = {}; - if (this.autoHide) { - hostStyle = { display: this.isShown ? 'block' : 'none' }; - } + const classes = { + 'is-shown': (this.isShown && this.animation === 'none') || this.animation === undefined, + 'is-hidden': (!this.isShown && this.animation === 'none') || this.animation === undefined, + 'initially-hidden': !this.hasShownAtLeastOnce, + 'tds-animation-enter-fade': this.isShown && this.animation === 'fade', + 'tds-animation-exit-fade': !this.isShown && this.animation === 'fade', + }; + + const classList = generateClassList(classes); return ( - + ); diff --git a/packages/core/src/components/popover-core/readme.md b/packages/core/src/components/popover-core/readme.md index 530b966bd..7d53831ae 100644 --- a/packages/core/src/components/popover-core/readme.md +++ b/packages/core/src/components/popover-core/readme.md @@ -8,6 +8,7 @@ | Property | Attribute | Description | Type | Default | | ---------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | +| `animation` | `animation` | Whether the popover should animate when being opened/closed or not | `string` | `'none'` | | `autoHide` | `auto-hide` | Decides if the popover should hide automatically. Alternatevly it can be hidden externally based on emitted events. | `boolean` | `true` | | `defaultShow` | `default-show` | Decides if the component should be visible from the start. | `boolean` | `false` | | `modifiers` | -- | Array of modifier objects to pass to popper.js. See https://popper.js.org/docs/v2/modifiers/ | `Object[]` | `[]` | diff --git a/packages/core/src/components/popover-core/tds-popover-core.scss b/packages/core/src/components/popover-core/tds-popover-core.scss new file mode 100644 index 000000000..0ebbc7194 --- /dev/null +++ b/packages/core/src/components/popover-core/tds-popover-core.scss @@ -0,0 +1,34 @@ +@import '../../global/global'; + +:host { + pointer-events: none; + display: block; +} + +:host(.tds-animation-enter-fade) { + animation: tds-fade-in var(--tds-motion-duration-moderate-01) var(--tds-motion-easing-enter) + forwards; +} + +:host(.tds-animation-exit-fade) { + animation: tds-fade-out var(--tds-motion-duration-moderate-01) var(--tds-motion-easing-exit) + forwards; +} + +:host(.is-shown) { + opacity: 1; + pointer-events: auto; + visibility: visible; +} + +:host(.is-hidden) { + opacity: 0; + pointer-events: none; + visibility: hidden; +} + +:host(.initially-hidden) { + opacity: 0; + pointer-events: none; + visibility: hidden; +} diff --git a/packages/core/src/components/popover-menu/popover-menu-item/popover-menu-item.scss b/packages/core/src/components/popover-menu/popover-menu-item/popover-menu-item.scss index de168ec15..8dfc53fdf 100644 --- a/packages/core/src/components/popover-menu/popover-menu-item/popover-menu-item.scss +++ b/packages/core/src/components/popover-menu/popover-menu-item/popover-menu-item.scss @@ -13,6 +13,7 @@ width: 100%; display: flex; align-items: center; + transition: background-color var(--tds-motion-duration-fast-02) var(--tds-motion-easing-easy); &:hover { cursor: pointer; diff --git a/packages/core/src/components/popover-menu/popover-menu.stories.tsx b/packages/core/src/components/popover-menu/popover-menu.stories.tsx index e48d78522..2972797c0 100644 --- a/packages/core/src/components/popover-menu/popover-menu.stories.tsx +++ b/packages/core/src/components/popover-menu/popover-menu.stories.tsx @@ -61,15 +61,27 @@ export default { type: 'boolean', }, }, + animation: { + name: 'Animation', + description: 'Sets the animation of the Popover Menu.', + control: { + type: 'radio', + }, + options: ['none', 'fade'], + table: { + defaultValue: { summary: 'none' }, + }, + }, }, args: { menuPosition: 'Auto', icons: false, fluidWidth: false, + animation: 'none', }, }; -const Template = ({ menuPosition, icons, fluidWidth }) => { +const Template = ({ menuPosition, icons, fluidWidth, animation }) => { const menuPosLookup = { 'Bottom': 'bottom', 'Bottom start': 'bottom-start', @@ -101,6 +113,7 @@ const Template = ({ menuPosition, icons, fluidWidth }) => { diff --git a/packages/core/src/components/popover-menu/popover-menu.tsx b/packages/core/src/components/popover-menu/popover-menu.tsx index 1fe119eca..9353be885 100644 --- a/packages/core/src/components/popover-menu/popover-menu.tsx +++ b/packages/core/src/components/popover-menu/popover-menu.tsx @@ -33,6 +33,9 @@ export class TdsPopoverMenu { /** Decides the placement of the Popover Menu */ @Prop() placement: Placement = 'auto'; + /** Whether the popover should animate when being opened/closed or not */ + @Prop() animation: 'none' | 'fade' | string = 'none'; + /** Sets the offset skidding */ @Prop() offsetSkidding: number = 0; @@ -74,6 +77,7 @@ export class TdsPopoverMenu { this.childRef = el; }} defaultShow={this.defaultShow} + animation={this.animation} >
diff --git a/packages/core/src/components/popover-menu/readme.md b/packages/core/src/components/popover-menu/readme.md index 049f54aca..f50832ab9 100644 --- a/packages/core/src/components/popover-menu/readme.md +++ b/packages/core/src/components/popover-menu/readme.md @@ -71,6 +71,7 @@ Example: | Property | Attribute | Description | Type | Default | | ---------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | +| `animation` | `animation` | Whether the popover should animate when being opened/closed or not | `string` | `'none'` | | `defaultShow` | `default-show` | Decides if the component should be visible from the start. | `boolean` | `false` | | `fluidWidth` | `fluid-width` | If true this unsets the width (160px) of the Popover Menu | `boolean` | `false` | | `offsetDistance` | `offset-distance` | Sets the offset distance | `number` | `8` | diff --git a/packages/core/src/utils/classList.ts b/packages/core/src/utils/classList.ts new file mode 100644 index 000000000..5bdbd9654 --- /dev/null +++ b/packages/core/src/utils/classList.ts @@ -0,0 +1,22 @@ +/** + * Generates a string of class names by filtering out keys from the `classes` object + * whose values evaluate to `false`. The keys that remain will be joined with a space. + * + * @param classes - An object where keys represent class names and values are boolean + * flags indicating whether to include the class name. + * @returns A string of class names separated by spaces. + * + * @example + * const classes = { + * 'active': true, + * 'disabled': false, + * 'highlighted': true, + * }; + * const classList = generateClassList(classes); + * console.log(classList); // Output: "active highlighted" + */ +export const generateClassList = (classes: Record): string => { + return Object.keys(classes) + .filter((key) => classes[key]) + .join(' '); +};