Skip to content
Closed
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
153 changes: 153 additions & 0 deletions blocks/shared/da-shortcuts-modal/da-shortcuts-modal.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
:host > svg {
display: none;
}

sl-dialog::part(body) {
padding: 0;
}

sl-dialog::part(panel) {
max-width: 90vw;
}

svg.icon {
display: block;
width: 20px;
height: 20px;
}

.shortcuts-modal {
padding: 24px;
width: 700px;
max-width: 90vw;
box-sizing: border-box;
}

.shortcuts-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 24px;
user-select: none;
}

.shortcuts-header h2 {
margin: 0;
font-size: 24px;
font-weight: 600;
line-height: 1.2;
}

.shortcuts-header .close-btn {
display: flex;
justify-content: center;
align-items: center;
width: 32px;
height: 32px;
border: none;
padding: 0;
background: transparent;
border-radius: 4px;
cursor: pointer;
}

.shortcuts-header .close-btn:hover {
background-color: var(--s2-gray-200);
}

.shortcuts-header .close-btn:focus-visible {
outline: 2px solid var(--s2-blue-400);
outline-offset: 2px;
}

.shortcuts-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 32px;
margin-bottom: 24px;
}

.shortcuts-column h3 {
margin: 0 0 12px;
font-size: 14px;
font-weight: 600;
color: var(--s2-gray-600);
text-transform: uppercase;
letter-spacing: 0.5px;
}

.shortcuts-column h3.section-title {
margin-top: 24px;
}

.shortcut-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid var(--s2-gray-100);
}

.shortcut-item:last-child {
border-bottom: none;
}

.shortcut-label {
font-size: 14px;
color: var(--s2-gray-800);
}

.shortcut-keys {
display: inline-flex;
align-items: center;
gap: 2px;
font-family: var(--body-font-family);
font-size: 13px;
font-weight: 600;
color: var(--s2-gray-700);
background-color: var(--s2-gray-100);
border: 1px solid var(--s2-gray-300);
border-radius: 4px;
padding: 4px 8px;
white-space: nowrap;
}

.shortcuts-footer {
display: flex;
justify-content: flex-end;
padding-top: 16px;
border-top: 1px solid var(--s2-gray-200);
}

.shortcuts-footer .close-button {
padding: 8px 20px;
font-size: 14px;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
background-color: var(--s2-blue-600);
color: white;
}

.shortcuts-footer .close-button:hover {
background-color: var(--s2-blue-700);
}

.shortcuts-footer .close-button:focus-visible {
outline: 2px solid var(--s2-blue-400);
outline-offset: 2px;
}

/* Responsive adjustments for smaller screens */
@media (max-width: 768px) {
.shortcuts-modal {
width: 100%;
padding: 16px;
}

.shortcuts-content {
grid-template-columns: 1fr;
gap: 24px;
}
}
146 changes: 146 additions & 0 deletions blocks/shared/da-shortcuts-modal/da-shortcuts-modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { LitElement, html } from 'da-lit';
import { getNx } from '../../../scripts/utils.js';

const nx = getNx();

// SL Components
await import(`${nx}/public/sl/components.js`);

// Styles
const { default: getStyle } = await import(`${nx}/utils/styles.js`);
const SL = await getStyle(`${nx}/public/sl/styles.css`);
const STYLE = await getStyle(import.meta.url);

// Detect platform
const isMac = typeof navigator !== 'undefined' && (
/Mac/.test(navigator.platform)
|| /Mac/.test(navigator.userAgentData?.platform)
|| navigator.platform === 'MacIntel'
);

export default class DaShortcutsModal extends LitElement {
static properties = { _shortcuts: { state: true } };

constructor() {
super();
this._shortcuts = null;
this._loadShortcuts();
}

async _loadShortcuts() {
try {
const response = await fetch('/blocks/shared/da-shortcuts-modal/shortcuts.json');
this._shortcuts = await response.json();
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to load shortcuts:', error);
this._shortcuts = { categories: [] };
}
}

connectedCallback() {
super.connectedCallback();
this.shadowRoot.adoptedStyleSheets = [SL, STYLE];
setTimeout(() => { this.showModal(); }, 20);

// Handle '?' key to close (custom behavior - ESC is handled by sl-dialog)
this._handleKeyDown = (e) => {
if (e.key === '?') {
e.preventDefault();
e.stopPropagation();
this.close();
}
};

// Delay to avoid catching the keypress that opened the modal
setTimeout(() => {
document.addEventListener('keydown', this._handleKeyDown, true);
}, 100);
}

disconnectedCallback() {
super.disconnectedCallback();
document.removeEventListener('keydown', this._handleKeyDown, true);
}

get _dialog() {
return this.shadowRoot.querySelector('sl-dialog');
}

showModal() {
this._dialog?.showModal();
}

close() {
document.removeEventListener('keydown', this._handleKeyDown, true);
this._dialog?.close();

const event = new CustomEvent('close', { bubbles: true, composed: true });
this.dispatchEvent(event);

// Remove from DOM
setTimeout(() => { this.remove(); }, 0);
}

renderShortcut(shortcut) {
const platform = isMac ? 'mac' : 'windows';
const keys = shortcut.keys[platform];
return html`
<div class="shortcut-item">
<span class="shortcut-label">${shortcut.label}</span>
<kbd class="shortcut-keys">${keys}</kbd>
</div>
`;
}

renderCategory(category) {
return html`
<div class="shortcuts-column">
<h3>${category.name}</h3>
${category.shortcuts.map((shortcut) => this.renderShortcut(shortcut))}
</div>
`;
}

render() {
if (!this._shortcuts) {
return html`<sl-dialog><div class="shortcuts-modal">Loading...</div></sl-dialog>`;
}

return html`
<sl-dialog @close=${this.close}>
<div class="shortcuts-modal" role="dialog" aria-labelledby="shortcuts-title" aria-modal="true">
<div class="shortcuts-header">
<h2 id="shortcuts-title">Keyboard shortcuts</h2>
<button
class="close-btn"
@click=${this.close}
aria-label="Close shortcuts dialog">
<svg class="icon" aria-hidden="true"><use href="/blocks/browse/img/S2IconClose20N-icon.svg#S2IconClose20N-icon"></use></svg>
</button>
</div>

<div class="shortcuts-content">
${this._shortcuts.categories.map((category) => this.renderCategory(category))}
</div>

<div class="shortcuts-footer">
<button class="close-button" @click=${this.close}>Close</button>
</div>
</div>
</sl-dialog>
`;
}
}

customElements.define('da-shortcuts-modal', DaShortcutsModal);

export function openShortcutsModal() {
// Remove any existing modal
document.querySelectorAll('da-shortcuts-modal').forEach((modal) => modal.remove());

// Create and show new modal
const modal = document.createElement('da-shortcuts-modal');
document.body.appendChild(modal);
return modal;
}
Loading