Skip to content

Commit

Permalink
Add new levels of notification for dev toolbar apps (#10252)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
Princesseuh and natemoo-re committed Mar 8, 2024
1 parent a31bbd7 commit 3307cb3
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 25 deletions.
17 changes: 17 additions & 0 deletions .changeset/hungry-needles-cough.md
@@ -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",
},
})
);
```
14 changes: 14 additions & 0 deletions packages/astro/e2e/dev-toolbar.test.js
Expand Up @@ -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('/'));

Expand Down
10 changes: 9 additions & 1 deletion packages/astro/e2e/fixtures/dev-toolbar/custom-plugin.js
Expand Up @@ -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';
Expand All @@ -13,6 +13,14 @@ export default {
console.log('Clicked!');
});

eventTarget.dispatchEvent(
new CustomEvent("toggle-notification", {
detail: {
level: "warning",
},
})
);

astroWindow.appendChild(myButton);

canvas.appendChild(astroWindow);
Expand Down
71 changes: 55 additions & 16 deletions packages/astro/src/runtime/client/dev-toolbar/entrypoint.ts
Expand Up @@ -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) => {
if (!(evt instanceof CustomEvent)) return;

const target = overlay.shadowRoot?.querySelector(`[data-app-id="${app.id}"]`);
if (!target) return;
const notificationElement = target?.querySelector('.notification');
if (!target || !notificationElement) return;

let newState = true;
if (evt instanceof CustomEvent) {
newState = 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';

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) => {
Expand Down Expand Up @@ -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] {
Expand Down Expand Up @@ -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',
},
})
);
Expand Down
18 changes: 10 additions & 8 deletions packages/astro/src/runtime/client/dev-toolbar/toolbar.ts
Expand Up @@ -9,6 +9,7 @@ export type DevToolbarApp = DevToolbarAppDefinition & {
status: 'ready' | 'loading' | 'error';
notification: {
state: boolean;
level?: 'error' | 'warning' | 'info';
};
eventTarget: EventTarget;
};
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
Expand All @@ -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] {
Expand Down

0 comments on commit 3307cb3

Please sign in to comment.