diff --git a/docs/components/popup.md b/docs/components/popup.md index c48a3c0d..0321b70b 100644 --- a/docs/components/popup.md +++ b/docs/components/popup.md @@ -681,50 +681,96 @@ const App = () => { Add an arrow to your popup with the `arrow` attribute. It's usually a good idea to set a `distance` to make room for the arrow. To adjust the arrow's color and size, use the `--arrow-color` and `--arrow-size` custom properties, respectively. You can also target the `arrow` part to add additional styles such as shadows and borders. +By default, the arrow will be aligned as close to the center of the _anchor_ as possible, considering available space and `arrow-padding`. You can use the `arrow-placement` attribute to force the arrow to align to the start, end, or center of the _popup_ instead. + ```html preview - - - - ``` ```jsx react import { useState } from 'react'; -import { SlPopup, SlSwitch } from '@shoelace-style/shoelace/dist/react'; +import { SlPopup, SlSelect, SlMenuItem, SlSwitch } from '@shoelace-style/shoelace/dist/react'; const css = ` .popup-arrow sl-popup { @@ -745,23 +791,76 @@ const css = ` background: var(--sl-color-primary-600); border-radius: var(--sl-border-radius-medium); } + + .popup-arrow-options { + display: flex; + flex-wrap: wrap; + align-items: end; + gap: 1rem; + } + + .popup-arrow-options sl-select { + width: 160px; + } + + .popup-arrow-options + .popup-arrow-options { + margin-top: 1rem; + } `; const App = () => { + const [placement, setPlacement] = useState('top'); + const [arrowPlacement, setArrowPlacement] = useState('anchor'); const [arrow, setArrow] = useState(true); return ( <>
- +
-
- setArrow(event.target.checked)}> - Arrow - +
+ setPlacement(event.target.value)} + > + top + top-start + top-end + bottom + bottom-start + bottom-end + right + right-start + right-end + left + left-start + left-end + + + setArrowPlacement(event.target.value)} + > + anchor + start + end + center + +
+ +
+ setArrow(event.target.checked)}> + Arrow + +
diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md index 22c064f7..a823117f 100644 --- a/docs/resources/changelog.md +++ b/docs/resources/changelog.md @@ -10,7 +10,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis ## Next -- Added the `sync` attribute to `` +- Added the `sync` and `arrow-placement` attributes to `` - Changed the `auto-size` attribute of the experimental `` component so it accepts `horizontal`, `vertical`, and `both` instead of a boolean value - Changed the `flip-fallback-placement` attribute of the experimental `` component to `flip-fallback-placements` - Changed the `flip-fallback-strategy` in the experimental `` component to accept `best-fit` and `initial` instead of `bestFit` and `initialPlacement` diff --git a/src/components/popup/popup.ts b/src/components/popup/popup.ts index 6737ade1..e97830b4 100644 --- a/src/components/popup/popup.ts +++ b/src/components/popup/popup.ts @@ -93,6 +93,13 @@ export default class SlPopup extends ShoelaceElement { */ @property({ type: Boolean }) arrow = false; + /** + * The placement of the arrow. The default is `anchor`, which will align the arrow as close to the center of the + * anchor as possible, considering available space and `arrow-padding`. A value of `start`, `end`, or `center` will + * align the arrow to the start, end, or center of the popover instead. + */ + @property({ attribute: 'arrow-placement' }) arrowPlacement: 'start' | 'end' | 'center' | 'anchor' = 'anchor'; + /** * The amount of padding between the arrow and the edges of the popup. If the popup has a border-radius, for example, * this will prevent it from overflowing the corners. @@ -372,14 +379,36 @@ export default class SlPopup extends ShoelaceElement { }); if (this.arrow) { - const arrowX = middlewareData.arrow?.x; - const arrowY = middlewareData.arrow?.y; + const arrowX = middlewareData.arrow!.x; + const arrowY = middlewareData.arrow!.y; + let top = ''; + let right = ''; + let bottom = ''; + let left = ''; + + if (this.arrowPlacement === 'start') { + // Start + left = typeof arrowX === 'number' ? `${this.arrowPadding}px` : ''; + top = typeof arrowY === 'number' ? `${this.arrowPadding}px` : ''; + } else if (this.arrowPlacement === 'end') { + // End + right = typeof arrowX === 'number' ? `${this.arrowPadding}px` : ''; + bottom = typeof arrowY === 'number' ? `${this.arrowPadding}px` : ''; + } else if (this.arrowPlacement === 'center') { + // Center + left = typeof arrowX === 'number' ? `calc(50% - var(--arrow-size))` : ''; + top = typeof arrowY === 'number' ? `calc(50% - var(--arrow-size))` : ''; + } else { + // Anchor (default) + left = typeof arrowX === 'number' ? `${arrowX}px` : ''; + top = typeof arrowY === 'number' ? `${arrowY}px` : ''; + } Object.assign(this.arrowEl.style, { - left: typeof arrowX === 'number' ? `${arrowX}px` : '', - top: typeof arrowY === 'number' ? `${arrowY}px` : '', - right: '', - bottom: '', + top, + right, + bottom, + left, [staticSide]: 'calc(var(--arrow-size) * -1)' }); }