diff --git a/apps/svelte.dev/src/lib/components/ModalDropdown.svelte b/apps/svelte.dev/src/lib/components/ModalDropdown.svelte new file mode 100644 index 0000000000..c5456c24ae --- /dev/null +++ b/apps/svelte.dev/src/lib/components/ModalDropdown.svelte @@ -0,0 +1,142 @@ + + + { + if (e.key === 'Escape') { + open = false; + } + }} +/> + + +
{ + const details = e.target as HTMLDetailsElement; + + if (details === e.currentTarget || !details.open) { + return; + } + + details.scrollIntoView(); + }} + ontoggle={(e) => { + const details = e.currentTarget; + if (!details.open) return; + + // close all details elements... + for (const child of details.querySelectorAll('details[open]')) { + (child as HTMLDetailsElement).open = false; + } + + // except parents of the current one + const current = details.querySelector(`[href="${$page.url.pathname}"]`) as HTMLAnchorElement | null; + if (!current) return; + + let node = current as Element; + + while ((node = (node.parentNode) as Element) && node !== details) { + if (node.nodeName === 'DETAILS') { + (node as HTMLDetailsElement).open = true; + } + } + + current.scrollIntoView(); + current.focus(); + }} + onkeydown={(e) => { + if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { + const children = focusable_children(e.currentTarget); + + if (e.key === 'ArrowDown') { + children.next(); + } else { + children.prev(); + } + } + + if (document.activeElement?.nodeName === 'SUMMARY' && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) { + (document.activeElement.parentNode as HTMLDetailsElement).open = e.key === 'ArrowRight'; + } + }} +> + + +
+ {@render children()} +
+
+ + diff --git a/apps/svelte.dev/src/lib/components/SelectIcon.svelte b/apps/svelte.dev/src/lib/components/SelectIcon.svelte deleted file mode 100644 index 991221459b..0000000000 --- a/apps/svelte.dev/src/lib/components/SelectIcon.svelte +++ /dev/null @@ -1,60 +0,0 @@ - - -
- - -
- - diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/AppControls.svelte b/apps/svelte.dev/src/routes/(authed)/playground/[id]/AppControls.svelte index 7a894ffc35..a1b6917141 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/AppControls.svelte +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/AppControls.svelte @@ -1,12 +1,12 @@ - { - goto(`/tutorial/${e.currentTarget.value}`); - }} - > - {#each index as part} - - {#each part.chapters as chapter} - + +
+ {#each index as part} +
+ {part.title} + + {#each part.chapters as chapter} +
+ {chapter.title} - {#each chapter.exercises as exercise} - + +
{/each} - {/each} - - {/each} - +
+ {/each} +
+
elements, + // except for the elements that are their direct children node.querySelectorAll( - 'a[href], button, input, textarea, select, summary, [tabindex]:not([tabindex="-1"])' + ':where(a[href], button, input, textarea, select, summary, [tabindex]:not([tabindex="-1"])):not(details:not([open]) *), summary:not(details:not([open]) details *)' ) ); @@ -31,10 +33,6 @@ export function focusable_children(node: HTMLElement) { while ((node = reordered[i])) { i += d; - if (node.matches('details:not([open]) *')) { - continue; - } - if (!selector || node.matches(selector)) { node.focus(); return; diff --git a/packages/site-kit/src/lib/styles/index.css b/packages/site-kit/src/lib/styles/index.css index e336fe3089..af9bf0c95a 100644 --- a/packages/site-kit/src/lib/styles/index.css +++ b/packages/site-kit/src/lib/styles/index.css @@ -12,4 +12,5 @@ @import './text.css'; @import './utils/buttons.css'; @import './utils/dividers.css'; +@import './utils/nav.css'; @import './utils/twoslash.css'; diff --git a/packages/site-kit/src/lib/styles/utils/nav.css b/packages/site-kit/src/lib/styles/utils/nav.css new file mode 100644 index 0000000000..f5ef788bd4 --- /dev/null +++ b/packages/site-kit/src/lib/styles/utils/nav.css @@ -0,0 +1,53 @@ +.secondary-nav-dropdown { + max-height: 50rem; + width: 30rem; + max-width: calc(100vw - 2 * var(--sk-page-padding-side) - 2rem); + font: var(--sk-font-ui-medium); + + details { + padding-left: 1rem; + + summary { + position: relative; + font: inherit; + display: block; + user-select: none; + + &::before { + content: ''; + position: absolute; + top: 0.3rem; + left: -2rem; + width: 1.8rem; + height: 1.8rem; + background: url($lib/icons/chevron.svg) no-repeat 50% 50%; + background-size: 100%; + rotate: -90deg; + } + + [open] > &::before { + rotate: none; + } + } + + ul { + font: inherit; + list-style: none; + margin: 0; + padding-left: 1rem; + } + } + + & > details { + padding-left: 2rem; + } + + a:not([aria-current='page']) { + color: inherit; + } + + /* necessary for reasons i don't fully understand */ + & > details[open]:last-child:not(:has([aria-current='page'])) { + padding-bottom: 1rem; + } +}