Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(toolbar): allow the user to change the placement #10591

Merged
merged 17 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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
33 changes: 33 additions & 0 deletions packages/astro/e2e/dev-toolbar.test.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
DevToolbarCard,
DevToolbarHighlight,
DevToolbarIcon,
DevToolbarSelect,
DevToolbarToggle,
DevToolbarTooltip,
DevToolbarWindow,
Expand Down Expand Up @@ -2903,6 +2904,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
12 changes: 12 additions & 0 deletions packages/astro/src/runtime/client/dev-toolbar/apps/astro.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { DevToolbarApp, DevToolbarMetadata } from '../../../../@types/astro.js';
import { type Icon, isDefinedIcon } from '../ui-library/icons.js';
import type { Placement } from '../ui-library/window.js';
import { colorForIntegration, iconForIntegration } from './utils/icons.js';
import { closeOnOutsideClick, createWindowElement } from './utils/window.js';

Expand Down Expand Up @@ -43,6 +44,17 @@ export default {
if (!integrationData) fetchIntegrationData();
}
});
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;
});
Princesseuh marked this conversation as resolved.
Show resolved Hide resolved

closeOnOutsideClick(eventTarget);

Expand Down
46 changes: 45 additions & 1 deletion packages/astro/src/runtime/client/dev-toolbar/apps/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { DevToolbarApp } from '../../../../@types/astro.js';
import { type Settings, settings } from '../settings.js';
import { type Placement, isValidPlacement, placements } from '../ui-library/window.js';
import { closeOnOutsideClick, createWindowElement } from './utils/window.js';

interface SettingRow {
Expand Down Expand Up @@ -43,6 +44,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 @@ -54,6 +71,17 @@ export default {

document.addEventListener('astro:after-swap', createSettingsWindow);

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;
});
closeOnOutsideClick(eventTarget);

function createSettingsWindow() {
Expand Down Expand Up @@ -161,10 +189,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
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export function createWindowElement(content: string) {
import { settings } from '../../settings.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
12 changes: 12 additions & 0 deletions packages/astro/src/runtime/client/dev-toolbar/apps/xray.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { escape as escapeHTML } from 'html-escaper';
import type { DevToolbarApp, DevToolbarMetadata } from '../../../../@types/astro.js';
import type { DevToolbarHighlight } from '../ui-library/highlight.js';
import type { Placement } from '../ui-library/window.js';
import {
attachTooltipToHighlight,
createHighlight,
Expand All @@ -24,6 +25,17 @@ export default {
document.addEventListener('astro:after-swap', addIslandsOverlay);
document.addEventListener('astro:page-load', refreshIslandsOverlayPositions);

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;
});
closeOnOutsideClick(eventTarget);

function addIslandsOverlay() {
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/runtime/client/dev-toolbar/entrypoint.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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 @@ -240,9 +250,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 @@ -553,6 +561,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
Original file line number Diff line number Diff line change
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';
Loading
Loading