Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
27 changes: 15 additions & 12 deletions src/controllers/ElementsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -886,23 +886,26 @@ private function _additionalButtons(
if ($isDraft && !$isCurrent && $canSave && $canSaveCanonical) {
/** @phpstan-ignore-next-line */
$disabled = $canonical->hasMethod('isEntryTypeCompatible') && !$element->isEntryTypeCompatible();
$btnContent = Craft::t('app', 'Apply draft');
if ($disabled) {
$btnContent .= ' <craft-tooltip>' .
Craft::t(
'app',
'The Entry Type for this draft is no longer available. You can still view it, but not apply it.'
) .
'</craft-tooltip>';
}
$components[] = Html::button($btnContent, [
//TODO: uncomment or remove once Brian says if the tooltip can work on disabled elements too
'class' => ['btn', 'secondary', 'formsubmit', /*$disabled ? 'disabled' : ''*/],

$button = Html::button(Craft::t('app', 'Apply draft'), [
'class' => ['btn', 'secondary', 'formsubmit', $disabled ? 'disabled' : ''],
'disabled' => $disabled,
'data' => [
'action' => 'elements/apply-draft',
'redirect' => Craft::$app->getSecurity()->hashData('{cpEditUrl}'),
],
]);

if ($disabled) {
$button = Html::tag('craft-tooltip', $button, [
'aria-label' => Craft::t(
'app',
'The Entry Type for this draft is no longer available. You can still view it, but not apply it.'
),
]);
}

$components[] = $button;
}

// Revert content from this revision
Expand Down
6 changes: 3 additions & 3 deletions src/web/assets/cp/src/css/_craft-tooltip.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
craft-tooltip {
.craft-tooltip {
position: fixed;
white-space: normal;
opacity: 0;
Expand All @@ -13,7 +13,7 @@ craft-tooltip {
z-index: 99;
}

craft-tooltip > .inner {
.craft-tooltip > .inner {
position: relative;
display: inline-block;
background-color: var(--white);
Expand All @@ -27,7 +27,7 @@ craft-tooltip > .inner {
font-weight: 400;
}

craft-tooltip .arrow {
.craft-tooltip .arrow {
position: absolute;
background: var(--white);
width: 8px;
Expand Down
3 changes: 2 additions & 1 deletion src/web/assets/cp/src/js/CraftElementLabel.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ class CraftElementLabel extends HTMLElement {

createTooltip() {
this.tooltip = document.createElement('craft-tooltip');
this.tooltip.innerText = this.innerText;
this.tooltip.setAttribute('self-managed', 'true');
this.tooltip.setAttribute('aria-label', this.innerText);

// If there's a context label, make it a little nicer
const contextLabel = this.querySelector('.context-label');
Expand Down
46 changes: 35 additions & 11 deletions src/web/assets/cp/src/js/CraftTooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,28 @@ import {arrow, computePosition, flip, offset, shift} from '@floating-ui/dom';
* @property {'top'|'top-start'|'top-end'|'right'|'right-start'|'right-end'|'bottom'|'bottom-start'|'bottom-end'|'left'|'left-start'|'left-end'} placement - The placement of the tooltip relative to the parent element.
* @property {boolean} arrow - Whether the tooltip should have an arrow.
* @property {number} offset - The offset of the tooltip from the parent element.
* @property {boolean} self-managed - Whether the tooltip should manage its own state.
* @property {string} aria-label - Text content for the tooltip
*
* @method show - Show the tooltip.
* @method hide - Hide the tooltip.
* @method update - Update the position of the tooltip.
* @example <craft-tooltip arrow="false" placement="top" offset="10">Tooltip content</craft-tooltip>
*
* @example <craft-tooltip aria-label="Tooltip content"><button type="button">Trigger</button></craft-tooltip>
*/
class CraftTooltip extends HTMLElement {
connectedCallback() {
this.arrowElement = this.querySelector('.arrow');
this.trigger = this.querySelector('a, button, [role="button"]');
this.selfManaged = this.hasAttribute('self-managed');

this.arrow = this.getAttribute('arrow') !== 'false';
this.offset = this.hasAttribute('offset')
? parseInt(this.getAttribute('offset'), 10)
: 8;

if (this.arrow && !this.arrowElement) {
this.renderTooltip();
this.renderInner();
this.renderArrow();
}
Expand All @@ -38,8 +45,20 @@ class CraftTooltip extends HTMLElement {
['blur', this.hide],
];

if (this.selfManaged) {
this.trigger = this.parentElement;
}

if (!this.trigger) {
console.log('No trigger found for tooltip');
return false;
}

// Make sure the trigger accepts pointer events
this.trigger.style.pointerEvents = 'auto';

this.listeners.forEach(([event, handler]) => {
this.parentElement?.addEventListener(event, handler.bind(this));
this.trigger?.addEventListener(event, handler.bind(this));
});

// Close on ESC
Expand All @@ -51,7 +70,7 @@ class CraftTooltip extends HTMLElement {

if (this.listeners.length) {
this.listeners.forEach(([event, handler]) => {
this.parentElement?.removeEventListener(event, handler.bind(this));
this.trigger?.removeEventListener(event, handler.bind(this));
});
}

Expand All @@ -64,18 +83,23 @@ class CraftTooltip extends HTMLElement {
}
}

renderTooltip() {
this.tooltip = document.createElement('span');
this.tooltip.classList.add('craft-tooltip');
this.appendChild(this.tooltip);
}

/**
* Renders an inner container so we can use padding for the offset and
* maintain a better hover experience for users using zoom.
*/
renderInner() {
this.inner = document.createElement('span');
this.inner.classList.add('inner');
this.inner.innerText = this.innerText;
this.inner.innerText = this.getAttribute('aria-label');

// Replace the content with the inner container
this.innerHTML = '';
this.appendChild(this.inner);
this.tooltip.appendChild(this.inner);
}

renderArrow() {
Expand All @@ -86,7 +110,7 @@ class CraftTooltip extends HTMLElement {

show() {
this.update();
Object.assign(this.style, {
Object.assign(this.tooltip.style, {
opacity: 1,
transform: `translateY(0)`,
// Make sure if a user hovers over the label itself, it stays open
Expand All @@ -95,15 +119,15 @@ class CraftTooltip extends HTMLElement {
}

hide() {
Object.assign(this.style, {
Object.assign(this.tooltip.style, {
opacity: 0,
transform: `translateY(5px)`,
pointerEvents: 'none',
});
}

update() {
computePosition(this.parentElement, this, {
computePosition(this.trigger, this.tooltip, {
strategy: 'fixed',
placement: this.getAttribute('placement') || 'bottom',
middleware: [
Expand All @@ -113,7 +137,7 @@ class CraftTooltip extends HTMLElement {
...(this.arrow ? [arrow({element: this.arrowElement})] : []),
],
}).then(({x, y, middlewareData, placement}) => {
Object.assign(this.style, {
Object.assign(this.tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
Expand All @@ -131,7 +155,7 @@ class CraftTooltip extends HTMLElement {
}[placement.split('-')[0]];

// Add padding to the static side for accessible hovers
Object.assign(this.style, {
Object.assign(this.tooltip.style, {
[`padding${Craft.uppercaseFirst(staticSide)}`]: `${this.offset}px`,
});

Expand Down