Skip to content

Commit

Permalink
feat(toolbar): allow the user to change the placement (#10591)
Browse files Browse the repository at this point in the history
* feat(toolbar): add `placement` to settings

* feat(toolbar): update `settings.placement` with `<select>`

* feat(toolbar): adjust position based on `settings.placement`

* test(toolbar): add a test case for `settings.placement`

* refactor(toolbar): extract select element from settings app

* feat(toolbar): allow select element to have colors

* test(toolbar): fix failed test case

* refactor(toolbar): add `placement` property to window element

* refactor(toolbar): notify apps when placement changes

* test(toolbar): fix failed test case

* refactor(toolbar): extract `synchronizePlacementOnUpdate` function

* chore: add changeset

* chore: update changeset

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

---------

Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
  • Loading branch information
3 people committed Apr 10, 2024
1 parent 3807e3e commit 39988ef
Show file tree
Hide file tree
Showing 13 changed files with 313 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/dry-eels-yell.md
@@ -0,0 +1,5 @@
---
"astro": minor
---

Adds a new dev toolbar settings option to change the horizontal placement of the dev toolbar on your screen: bottom left, bottom center, or bottom right.
33 changes: 33 additions & 0 deletions packages/astro/e2e/dev-toolbar.test.js
Expand Up @@ -349,4 +349,37 @@ test.describe('Dev Toolbar', () => {
await expect(appButton).not.toHaveClass('active');
}
});

test('can adjust the placement', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/audit-no-warning'));

const toolbar = page.locator('astro-dev-toolbar');
const settingsAppButton = toolbar.locator('button[data-app-id="astro:settings"]');
await settingsAppButton.click();

const settingsAppCanvas = toolbar.locator(
'astro-dev-toolbar-app-canvas[data-app-id="astro:settings"]'
);
const settingsWindow = settingsAppCanvas.locator('astro-dev-toolbar-window');
await expect(settingsWindow).toBeVisible();

for (const placement of ['bottom-left', 'bottom-center', 'bottom-right']) {
const select = toolbar.getByRole('combobox');
await expect(select).toBeVisible();
await select.selectOption(placement);

const toolbarRoot = toolbar.locator('#dev-toolbar-root');
await expect(toolbarRoot).toHaveAttribute('data-placement', placement);

for (const appId of ['astro:home', 'astro:xray', 'astro:settings']) {
const appButton = toolbar.locator(`button[data-app-id="${appId}"]`);
await appButton.click();

const appCanvas = toolbar.locator(`astro-dev-toolbar-app-canvas[data-app-id="${appId}"]`);
const appWindow = appCanvas.locator('astro-dev-toolbar-window');
await expect(appWindow).toBeVisible();
await expect(appWindow).toHaveJSProperty('placement', placement);
}
}
});
});
2 changes: 2 additions & 0 deletions packages/astro/src/@types/astro.ts
Expand Up @@ -28,6 +28,7 @@ import type {
DevToolbarCard,
DevToolbarHighlight,
DevToolbarIcon,
DevToolbarSelect,
DevToolbarToggle,
DevToolbarTooltip,
DevToolbarWindow,
Expand Down Expand Up @@ -3003,6 +3004,7 @@ declare global {
'astro-dev-toolbar-button': DevToolbarButton;
'astro-dev-toolbar-icon': DevToolbarIcon;
'astro-dev-toolbar-card': DevToolbarCard;
'astro-dev-toolbar-select': DevToolbarSelect;

// Deprecated names
// TODO: Remove in Astro 5.0
Expand Down
7 changes: 6 additions & 1 deletion packages/astro/src/runtime/client/dev-toolbar/apps/astro.ts
@@ -1,7 +1,11 @@
import type { DevToolbarApp, DevToolbarMetadata } from '../../../../@types/astro.js';
import { type Icon, isDefinedIcon } from '../ui-library/icons.js';
import { colorForIntegration, iconForIntegration } from './utils/icons.js';
import { closeOnOutsideClick, createWindowElement } from './utils/window.js';
import {
closeOnOutsideClick,
createWindowElement,
synchronizePlacementOnUpdate,
} from './utils/window.js';

const astroLogo =
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 99 26" width="100"><path fill="#fff" d="M6.70402 22.1453c-1.17459-1.0737-1.51748-3.3297-1.02811-4.9641.84853 1.0304 2.02424 1.3569 3.24204 1.5411 1.88005.2844 3.72635.178 5.47285-.6813.1998-.0984.3844-.2292.6027-.3617.1639.4755.2065.9554.1493 1.4439-.1392 1.1898-.7313 2.1088-1.673 2.8054-.3765.2787-.775.5278-1.1639.7905-1.1948.8075-1.518 1.7544-1.0691 3.1318.0107.0336.0202.0671.0444.149-.6101-.273-1.0557-.6705-1.39518-1.1931-.3586-.5517-.52921-1.1619-.53819-1.8221-.00449-.3213-.00449-.6455-.0477-.9623-.10551-.7722-.46804-1.118-1.15102-1.1379-.70094-.0205-1.2554.4129-1.40244 1.0953-.01122.0523-.02749.1041-.04377.1649l.00112.0006Z"/><path fill="url(#paint0_linear_386_2739)" d="M6.70402 22.1453c-1.17459-1.0737-1.51748-3.3297-1.02811-4.9641.84853 1.0304 2.02424 1.3569 3.24204 1.5411 1.88005.2844 3.72635.178 5.47285-.6813.1998-.0984.3844-.2292.6027-.3617.1639.4755.2065.9554.1493 1.4439-.1392 1.1898-.7313 2.1088-1.673 2.8054-.3765.2787-.775.5278-1.1639.7905-1.1948.8075-1.518 1.7544-1.0691 3.1318.0107.0336.0202.0671.0444.149-.6101-.273-1.0557-.6705-1.39518-1.1931-.3586-.5517-.52921-1.1619-.53819-1.8221-.00449-.3213-.00449-.6455-.0477-.9623-.10551-.7722-.46804-1.118-1.15102-1.1379-.70094-.0205-1.2554.4129-1.40244 1.0953-.01122.0523-.02749.1041-.04377.1649l.00112.0006Z"/><path fill="#fff" d="M0 16.909s3.47815-1.6944 6.96603-1.6944l2.62973-8.13858c.09846-.39359.38592-.66106.71044-.66106.3246 0 .612.26747.7105.66106l2.6297 8.13858c4.1309 0 6.966 1.6944 6.966 1.6944S14.7045.814589 14.693.782298C14.5234.306461 14.2371 0 13.8512 0H6.76183c-.38593 0-.66063.306461-.84174.782298C5.90733.81398 0 16.909 0 16.909ZM36.671 11.7318c0 1.4262-1.7739 2.2779-4.2302 2.2779-1.5985 0-2.1638-.3962-2.1638-1.2281 0-.8715.7018-1.2875 2.3003-1.2875 1.4426 0 2.6707.0198 4.0937.1981v.0396Zm.0195-1.7629c-.8772-.19808-2.2028-.31693-3.7818-.31693-4.6006 0-6.7644 1.08943-6.7644 3.62483 0 2.6344 1.4815 3.6446 4.9125 3.6446 2.9046 0 4.8735-.7328 5.5947-2.5354h.117c-.0195.4358-.039.8716-.039 1.2083 0 .931.156 1.0102.9162 1.0102h3.5869c-.1949-.5546-.3119-2.1194-.3119-3.4663 0-1.446.0585-2.5355.0585-4.00123 0-2.99098-1.7934-4.89253-7.4077-4.89253-2.4173 0-5.1074.41596-7.1543 1.03.1949.81213.4679 2.45617.6043 3.5258 1.774-.83193 4.2887-1.18847 6.2381-1.18847 2.6902 0 3.4309.61404 3.4309 1.86193v.4952ZM46.5325 12.5637c-.4874.0594-1.1502.0594-1.8325.0594-.7213 0-1.3841-.0198-1.8324-.0792 0 .1585-.0195.3367-.0195.4952 0 2.476 1.618 3.922 7.3102 3.922 5.3609 0 7.0958-1.4262 7.0958-3.9418 0-2.3769-1.1501-3.5456-6.238-3.8031-3.9573-.17827-4.3082-.61404-4.3082-1.10924 0-.57442.5068-.87154 3.158-.87154 2.7487 0 3.4894.37635 3.4894 1.16866v.17827c.3899-.01981 1.0917-.03961 1.813-.03961.6823 0 1.423.0198 1.8519.05942 0-.17827.0195-.33674.0195-.47539 0-2.91175-2.4172-3.86252-7.0958-3.86252-5.2634 0-7.0373 1.2875-7.0373 3.8031 0 2.25805 1.423 3.66445 6.472 3.88235 3.7233.1188 4.1327.5348 4.1327 1.1092 0 .6141-.6043.8914-3.2165.8914-3.0021 0-3.7623-.416-3.7623-1.2677v-.1189ZM63.6883 2.125c-1.423 1.32712-3.9768 2.65425-5.3998 3.01079.0195.73289.0195 2.07982.0195 2.81271l1.3061.01981c-.0195 1.40635-.039 3.10979-.039 4.23889 0 2.6344 1.3841 4.6152 5.6922 4.6152 1.813 0 3.0216-.1981 4.5226-.515-.1559-.9706-.3314-2.4562-.3898-3.5852-.8968.2971-2.0274.4556-3.275.4556-1.735 0-2.4368-.4754-2.4368-1.8422 0-1.1884 0-2.29767.0195-3.32768 2.2223.01981 4.4446.05943 5.7507.09904-.0195-1.03.0195-2.51559.078-3.50598-1.8909.03961-4.0157.05942-5.7702.05942.0195-.87154.039-1.70347.0585-2.5354h-.1365ZM75.3313 7.35427c.0195-1.03001.039-1.90156.0585-2.75329h-3.9183c.0585 1.70347.0585 3.44656.0585 6.00172 0 2.5553-.0195 4.3182-.0585 6.0018h4.4836c-.078-1.1885-.0975-3.189-.0975-4.8925 0-2.69388 1.0917-3.46638 3.5674-3.46638 1.1502 0 1.9689.13865 2.6902.39615.0195-1.01019.2144-2.97117.3314-3.84271-.7408-.21789-1.5595-.35655-2.5537-.35655-2.1249-.0198-3.6844.85174-4.4056 2.93156l-.156-.0198ZM94.8501 10.5235c0 2.1591-1.5595 3.1693-4.0157 3.1693-2.4368 0-3.9963-.9508-3.9963-3.1693 0-2.21846 1.579-3.05039 3.9963-3.05039 2.4367 0 4.0157.89135 4.0157 3.05039Zm4.0743-.099c0-4.29832-3.353-6.21968-8.09-6.21968-4.7566 0-7.9926 1.92136-7.9926 6.21968 0 4.2785 3.0216 6.5762 7.9731 6.5762 4.9904 0 8.1095-2.2977 8.1095-6.5762Z"/><defs><linearGradient id="paint0_linear_386_2739" x1="5.46011" x2="16.8017" y1="25.9999" y2="20.6412" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient></defs></svg>';
Expand Down Expand Up @@ -45,6 +49,7 @@ export default {
});

closeOnOutsideClick(eventTarget);
synchronizePlacementOnUpdate(eventTarget, canvas);

function fetchIntegrationData() {
fetch('https://astro.build/api/v1/dev-overlay/', {
Expand Down
42 changes: 40 additions & 2 deletions packages/astro/src/runtime/client/dev-toolbar/apps/settings.ts
@@ -1,6 +1,11 @@
import type { DevToolbarApp } from '../../../../@types/astro.js';
import { type Settings, settings } from '../settings.js';
import { closeOnOutsideClick, createWindowElement } from './utils/window.js';
import { isValidPlacement, placements } from '../ui-library/window.js';
import {
closeOnOutsideClick,
createWindowElement,
synchronizePlacementOnUpdate,
} from './utils/window.js';

interface SettingRow {
name: string;
Expand Down Expand Up @@ -43,6 +48,22 @@ const settingsRows = [
}
},
},
{
name: 'Placement',
description: 'Adjust the placement of the dev toolbar.',
input: 'select',
settingKey: 'placement',
changeEvent: (evt: Event) => {
if (evt.currentTarget instanceof HTMLSelectElement) {
const placement = evt.currentTarget.value;
if (isValidPlacement(placement)) {
document.querySelector('astro-dev-toolbar')?.setToolbarPlacement(placement);
settings.updateSetting('placement', placement);
settings.logger.verboseLog(`Placement set to ${placement}`);
}
}
},
},
] satisfies SettingRow[];

export default {
Expand All @@ -55,6 +76,7 @@ export default {
document.addEventListener('astro:after-swap', createSettingsWindow);

closeOnOutsideClick(eventTarget);
synchronizePlacementOnUpdate(eventTarget, canvas);

function createSettingsWindow() {
const windowElement = createWindowElement(
Expand Down Expand Up @@ -161,10 +183,26 @@ export default {
case 'checkbox': {
const astroToggle = document.createElement('astro-dev-toolbar-toggle');
astroToggle.input.addEventListener('change', setting.changeEvent);
astroToggle.input.checked = settings.config[setting.settingKey];
astroToggle.input.checked = settings.config[setting.settingKey] as boolean;
label.append(astroToggle);
break;
}
case 'select': {
const astroSelect = document.createElement('astro-dev-toolbar-select');
placements.forEach((placement) => {
const option = document.createElement('option');
option.setAttribute('value', placement);
if (placement === settings.config[setting.settingKey]) {
option.selected = true;
}
option.textContent =
`${placement.slice(0, 1).toUpperCase()}${placement.slice(1)}`.replace('-', ' ');
astroSelect.append(option);
});
astroSelect.element.addEventListener('change', setting.changeEvent);
label.append(astroSelect);
break;
}
default:
break;
}
Expand Down
@@ -1,6 +1,10 @@
export function createWindowElement(content: string) {
import { settings } from '../../settings.js';
import type { Placement } from '../../ui-library/window.js';

export function createWindowElement(content: string, placement = settings.config.placement) {
const windowElement = document.createElement('astro-dev-toolbar-window');
windowElement.innerHTML = content;
windowElement.placement = placement;
return windowElement;
}

Expand Down Expand Up @@ -30,3 +34,17 @@ export function closeOnOutsideClick(
}
});
}

export function synchronizePlacementOnUpdate(eventTarget: EventTarget, canvas: ShadowRoot) {
eventTarget.addEventListener('placement-updated', (evt) => {
if (!(evt instanceof CustomEvent)) {
return;
}
const windowElement = canvas.querySelector('astro-dev-toolbar-window');
if (!windowElement) {
return;
}
const event: CustomEvent<{ placement: Placement }> = evt;
windowElement.placement = event.detail.placement;
});
}
7 changes: 6 additions & 1 deletion packages/astro/src/runtime/client/dev-toolbar/apps/xray.ts
Expand Up @@ -7,7 +7,11 @@ import {
getElementsPositionInDocument,
positionHighlight,
} from './utils/highlight.js';
import { closeOnOutsideClick, createWindowElement } from './utils/window.js';
import {
closeOnOutsideClick,
createWindowElement,
synchronizePlacementOnUpdate,
} from './utils/window.js';

const icon =
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#fff" d="M7.9 1.5v-.4a1.1 1.1 0 0 1 2.2 0v.4a1.1 1.1 0 1 1-2.2 0Zm-6.4 8.6a1.1 1.1 0 1 0 0-2.2h-.4a1.1 1.1 0 0 0 0 2.2h.4ZM12 3.7a1.1 1.1 0 0 0 1.4-.7l.4-1.1a1.1 1.1 0 0 0-2.1-.8l-.4 1.2a1.1 1.1 0 0 0 .7 1.4Zm-9.7 7.6-1.2.4a1.1 1.1 0 1 0 .8 2.1l1-.4a1.1 1.1 0 1 0-.6-2ZM20.8 17a1.9 1.9 0 0 1 0 2.6l-1.2 1.2a1.9 1.9 0 0 1-2.6 0l-4.3-4.2-1.6 3.6a1.9 1.9 0 0 1-1.7 1.2A1.9 1.9 0 0 1 7.5 20L2.7 5a1.9 1.9 0 0 1 2.4-2.4l15 5a1.9 1.9 0 0 1 .2 3.4l-3.7 1.6 4.2 4.3ZM19 18.3 14.6 14a1.9 1.9 0 0 1 .6-3l3.2-1.5L5.1 5.1l4.3 13.3 1.5-3.2a1.9 1.9 0 0 1 3-.6l4.4 4.4.7-.7Z"/></svg>';
Expand All @@ -25,6 +29,7 @@ export default {
document.addEventListener('astro:page-load', refreshIslandsOverlayPositions);

closeOnOutsideClick(eventTarget);
synchronizePlacementOnUpdate(eventTarget, canvas);

function addIslandsOverlay() {
islandsOverlays.forEach(({ highlightElement }) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/runtime/client/dev-toolbar/entrypoint.ts
Expand Up @@ -23,6 +23,7 @@ document.addEventListener('DOMContentLoaded', async () => {
DevToolbarButton,
DevToolbarBadge,
DevToolbarIcon,
DevToolbarSelect,
},
] = await Promise.all([
loadDevToolbarApps() as DevToolbarAppDefinition[],
Expand All @@ -45,6 +46,7 @@ document.addEventListener('DOMContentLoaded', async () => {
customElements.define('astro-dev-toolbar-button', DevToolbarButton);
customElements.define('astro-dev-toolbar-badge', DevToolbarBadge);
customElements.define('astro-dev-toolbar-icon', DevToolbarIcon);
customElements.define('astro-dev-toolbar-select', DevToolbarSelect);

// Add deprecated names
// TODO: Remove in Astro 5.0
Expand Down
6 changes: 5 additions & 1 deletion packages/astro/src/runtime/client/dev-toolbar/settings.ts
@@ -1,11 +1,15 @@
import type { Placement } from './ui-library/window.js';

export interface Settings {
disableAppNotification: boolean;
verbose: boolean;
placement: Placement;
}

export const defaultSettings = {
disableAppNotification: false,
verbose: false,
placement: 'bottom-center',
} satisfies Settings;

export const settings = getSettings();
Expand All @@ -25,7 +29,7 @@ function getSettings() {
_settings = { ..._settings, ...JSON.parse(toolbarSettings) };
}

function updateSetting(key: keyof Settings, value: Settings[typeof key]) {
function updateSetting<Key extends keyof Settings>(key: Key, value: Settings[Key]) {
_settings[key] = value;
localStorage.setItem('astro:dev-toolbar:settings', JSON.stringify(_settings));
}
Expand Down
31 changes: 26 additions & 5 deletions packages/astro/src/runtime/client/dev-toolbar/toolbar.ts
Expand Up @@ -2,6 +2,7 @@
import type { DevToolbarApp as DevToolbarAppDefinition } from '../../../@types/astro.js';
import { settings } from './settings.js';
import { type Icon, getIconElement, isDefinedIcon } from './ui-library/icons.js';
import { type Placement } from './ui-library/window.js';

export type DevToolbarApp = DevToolbarAppDefinition & {
builtIn: boolean;
Expand Down Expand Up @@ -57,8 +58,6 @@ export class AstroDevToolbar extends HTMLElement {
#dev-toolbar-root {
position: fixed;
bottom: 0px;
left: 50%;
transform: translate(-50%, 0%);
z-index: 2000000010;
display: flex;
flex-direction: column;
Expand All @@ -75,6 +74,17 @@ export class AstroDevToolbar extends HTMLElement {
opacity: 0.2;
}
#dev-toolbar-root[data-placement="bottom-left"] {
left: 16px;
}
#dev-toolbar-root[data-placement="bottom-center"] {
left: 50%;
transform: translateX(-50%);
}
#dev-toolbar-root[data-placement="bottom-right"] {
right: 16px;
}
#dev-bar-hitbox-above,
#dev-bar-hitbox-below {
width: 100%;
Expand Down Expand Up @@ -246,9 +256,7 @@ export class AstroDevToolbar extends HTMLElement {
width: 1px;
}
</style>
<div id="dev-toolbar-root" data-hidden ${
settings.config.disableAppNotification ? 'data-no-notification' : ''
}>
<div id="dev-toolbar-root" data-hidden ${settings.config.disableAppNotification ? 'data-no-notification' : ''} data-placement="${settings.config.placement}">
<div id="dev-bar-hitbox-above"></div>
<div id="dev-bar">
<div id="bar-container">
Expand Down Expand Up @@ -559,6 +567,19 @@ export class AstroDevToolbar extends HTMLElement {
?.querySelector('#dropdown')
?.toggleAttribute('data-no-notification', !newStatus);
}

setToolbarPlacement(newPlacement: Placement) {
this.devToolbarContainer?.setAttribute('data-placement', newPlacement);
this.apps.forEach((app) => {
app.eventTarget.dispatchEvent(
new CustomEvent('placement-updated', {
detail: {
placement: newPlacement,
},
})
);
});
}
}

export class DevToolbarCanvas extends HTMLElement {
Expand Down
Expand Up @@ -3,6 +3,7 @@ export { DevToolbarButton } from './button.js';
export { DevToolbarCard } from './card.js';
export { DevToolbarHighlight } from './highlight.js';
export { DevToolbarIcon } from './icon.js';
export { DevToolbarSelect } from './select.js';
export { DevToolbarToggle } from './toggle.js';
export { DevToolbarTooltip } from './tooltip.js';
export { DevToolbarWindow } from './window.js';

0 comments on commit 39988ef

Please sign in to comment.