0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-17 22:44:24 -05:00

Add new levels of notification for dev toolbar apps (#10252)

* Add new levels of notification

* feat: proper support

* chore: changeset

* fix: remove unrelated change

* test: add test

* feat: implement new icons

* fix: go back to previous layout

* fix: custom app number

---------

Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
This commit is contained in:
Erika 2024-03-08 11:53:48 +01:00 committed by GitHub
parent a31bbd7ff8
commit 3307cb34f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 106 additions and 26 deletions

View file

@ -0,0 +1,17 @@
---
"astro": minor
---
Adds support for emitting warning and info notifications from dev toolbar apps.
When using the `toggle-notification` event, the severity can be specified through `detail.level`:
```ts
eventTarget.dispatchEvent(
new CustomEvent("toggle-notification", {
detail: {
level: "warning",
},
})
);
```

View file

@ -295,6 +295,20 @@ test.describe('Dev Toolbar', () => {
expect(clientRenderTime).not.toBe(null);
});
test('apps can show notifications', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const toolbar = page.locator('astro-dev-toolbar');
const appButton = toolbar.locator('button[data-app-id="my-plugin"]');
await appButton.click();
const customAppNotification = appButton.locator('.icon .notification');
await expect(customAppNotification).toHaveAttribute('data-active');
await expect(customAppNotification).toHaveAttribute('data-level', 'warning');
await expect(customAppNotification).toBeVisible();
});
test('can quit apps by clicking outside the window', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));

View file

@ -2,7 +2,7 @@ export default {
id: 'my-plugin',
name: 'My Plugin',
icon: 'astro:logo',
init(canvas) {
init(canvas, eventTarget) {
const astroWindow = document.createElement('astro-dev-toolbar-window');
const myButton = document.createElement('astro-dev-toolbar-button');
myButton.size = 'medium';
@ -13,6 +13,14 @@ export default {
console.log('Clicked!');
});
eventTarget.dispatchEvent(
new CustomEvent("toggle-notification", {
detail: {
level: "warning",
},
})
);
astroWindow.appendChild(myButton);
canvas.appendChild(astroWindow);

View file

@ -62,30 +62,47 @@ document.addEventListener('DOMContentLoaded', async () => {
overlay = document.createElement('astro-dev-toolbar');
const notificationLevels = ['error', 'warning', 'info'] as const;
const notificationSVGs: Record<(typeof notificationLevels)[number], string> = {
error:
'<svg viewBox="0 0 10 10"><rect width="9" height="9" x=".5" y=".5" fill="#B33E66" stroke="#13151A" rx="4.5"/></svg>',
warning:
'<svg xmlns="http://www.w3.org/2000/svg" width="12" height="10" fill="none"><path fill="#B58A2D" stroke="#13151A" d="M7.29904 1.25c-.57735-1-2.02073-1-2.59808 0l-3.4641 6C.65951 8.25 1.3812 9.5 2.5359 9.5h6.9282c1.1547 0 1.8764-1.25 1.299-2.25l-3.46406-6Z"/></svg>',
info: '<svg viewBox="0 0 10 10"><rect width="9" height="9" x=".5" y=".5" fill="#3645D9" stroke="#13151A" rx="1.5"/></svg>',
} as const;
const prepareApp = (appDefinition: DevToolbarAppDefinition, builtIn: boolean): DevToolbarApp => {
const eventTarget = new EventTarget();
const app = {
const app: DevToolbarApp = {
...appDefinition,
builtIn: builtIn,
active: false,
status: 'loading' as const,
notification: { state: false },
status: 'loading',
notification: { state: false, level: undefined },
eventTarget: eventTarget,
};
// Events apps can send to the overlay to update their status
eventTarget.addEventListener('toggle-notification', (evt) => {
const target = overlay.shadowRoot?.querySelector(`[data-app-id="${app.id}"]`);
if (!target) return;
if (!(evt instanceof CustomEvent)) return;
let newState = true;
if (evt instanceof CustomEvent) {
newState = evt.detail.state ?? true;
}
const target = overlay.shadowRoot?.querySelector(`[data-app-id="${app.id}"]`);
const notificationElement = target?.querySelector('.notification');
if (!target || !notificationElement) return;
let newState = evt.detail.state ?? true;
let level = notificationLevels.includes(evt?.detail?.level)
? (evt.detail.level as (typeof notificationLevels)[number])
: 'error';
app.notification.state = newState;
if (newState) app.notification.level = level;
target.querySelector('.notification')?.toggleAttribute('data-active', newState);
notificationElement.toggleAttribute('data-active', newState);
if (newState) {
notificationElement.setAttribute('data-level', level);
notificationElement.innerHTML = notificationSVGs[level];
}
});
const onToggleApp = async (evt: Event) => {
@ -137,12 +154,13 @@ document.addEventListener('DOMContentLoaded', async () => {
display: none;
position: absolute;
top: -4px;
right: -6px;
width: 8px;
height: 8px;
border-radius: 9999px;
border: 1px solid rgba(19, 21, 26, 1);
background: #B33E66;
right: -5px;
width: 12px;
height: 10px;
}
.notification svg {
display: block;
}
#dropdown:not([data-no-notification]) .notification[data-active] {
@ -222,12 +240,33 @@ document.addEventListener('DOMContentLoaded', async () => {
app.eventTarget.addEventListener('toggle-notification', (evt) => {
if (!(evt instanceof CustomEvent)) return;
notification.toggleAttribute('data-active', evt.detail.state ?? true);
let newState = evt.detail.state ?? true;
let level = notificationLevels.includes(evt?.detail?.level)
? (evt.detail.level as (typeof notificationLevels)[number])
: 'error';
notification.toggleAttribute('data-active', newState);
if (newState) {
notification.setAttribute('data-level', level);
notification.innerHTML = notificationSVGs[level];
}
app.notification.state = newState;
if (newState) app.notification.level = level;
eventTarget.dispatchEvent(
new CustomEvent('toggle-notification', {
detail: {
state: hiddenApps.some((p) => p.notification.state === true),
level:
['error', 'warning', 'info'].find((notificationLevel) =>
hiddenApps.some(
(p) =>
p.notification.state === true &&
p.notification.level === notificationLevel
)
) ?? 'error',
},
})
);

View file

@ -9,6 +9,7 @@ export type DevToolbarApp = DevToolbarAppDefinition & {
status: 'ready' | 'loading' | 'error';
notification: {
state: boolean;
level?: 'error' | 'warning' | 'info';
};
eventTarget: EventTarget;
};
@ -187,8 +188,8 @@ export class AstroDevToolbar extends HTMLElement {
}
}
#dev-bar #bar-container .item.active .notification {
border-color: rgba(71, 78, 94, 1);
#dev-bar #bar-container .item.active .notification rect, #dev-bar #bar-container .item.active .notification path {
stroke: rgba(71, 78, 94, 1);
}
#dev-bar .item .icon {
@ -198,7 +199,7 @@ export class AstroDevToolbar extends HTMLElement {
user-select: none;
}
#dev-bar .item svg {
#dev-bar .item .icon>svg {
width: 20px;
height: 20px;
display: block;
@ -216,11 +217,12 @@ export class AstroDevToolbar extends HTMLElement {
position: absolute;
top: -4px;
right: -6px;
width: 8px;
height: 8px;
border-radius: 9999px;
border: 1px solid rgba(19, 21, 26, 1);
background: #B33E66;
width: 10px;
height: 10px;
}
#dev-bar .item .notification svg {
display: block;
}
#dev-toolbar-root:not([data-no-notification]) #dev-bar .item .notification[data-active] {