From 867f6d7a10a8558088ef2e0753549e83511ff801 Mon Sep 17 00:00:00 2001 From: Vlad Moroz Date: Mon, 25 Nov 2024 11:38:28 +0100 Subject: [PATCH] [docs] Add side nav, dabble in mobile responsiveness (#857) --- docs/package.json | 2 + docs/postcss.config.js | 1 + .../demos/hero/css-modules/index.module.css | 4 +- .../dialog/demos/hero/tailwind/index.tsx | 6 +- docs/src/app/new/(content)/layout.css | 37 ++++ docs/src/app/new/(content)/layout.tsx | 158 +++++++++++++++++- docs/src/app/new/layout.css | 97 +++++++++-- docs/src/app/new/layout.tsx | 10 +- docs/src/breakpoints.css | 21 +++ docs/src/components/Select.css | 1 + docs/src/components/SideNav.css | 132 +++++++++++++++ docs/src/components/SideNav.tsx | 130 ++++++++++++++ docs/src/components/demo/Demo.tsx | 2 +- docs/src/components/demo/index.css | 2 +- docs/src/components/quick-nav/QuickNav.css | 34 ++-- docs/src/components/quick-nav/QuickNav.tsx | 10 +- docs/src/styles.css | 6 +- pnpm-lock.yaml | 72 ++++++++ 18 files changed, 682 insertions(+), 43 deletions(-) create mode 100644 docs/src/app/new/(content)/layout.css create mode 100644 docs/src/breakpoints.css create mode 100644 docs/src/components/SideNav.css create mode 100644 docs/src/components/SideNav.tsx diff --git a/docs/package.json b/docs/package.json index a65a1fcd19..3e735571ff 100644 --- a/docs/package.json +++ b/docs/package.json @@ -48,6 +48,7 @@ "lz-string": "^1.5.0", "next": "15.0.2", "postcss": "^8.4.47", + "postcss-custom-media": "^11.0.5", "postcss-import": "^16.1.0", "prop-types": "^15.8.1", "react": "19.0.0-rc-fb9a90fa48-20240614", @@ -64,6 +65,7 @@ "remark-mdx-frontmatter": "^5.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", + "scroll-into-view-if-needed": "3.1.0", "shiki": "^1.22.2", "to-vfile": "^8.0.0", "unist-util-visit": "^5.0.0", diff --git a/docs/postcss.config.js b/docs/postcss.config.js index ac6e4da4cd..b4c177ee1f 100644 --- a/docs/postcss.config.js +++ b/docs/postcss.config.js @@ -2,5 +2,6 @@ module.exports = { plugins: { 'postcss-import': {}, '@tailwindcss/postcss': {}, + 'postcss-custom-media': {}, }, }; diff --git a/docs/src/app/new/(content)/components/dialog/demos/hero/css-modules/index.module.css b/docs/src/app/new/(content)/components/dialog/demos/hero/css-modules/index.module.css index 251a477819..6e30e50b6d 100644 --- a/docs/src/app/new/(content)/components/dialog/demos/hero/css-modules/index.module.css +++ b/docs/src/app/new/(content)/components/dialog/demos/hero/css-modules/index.module.css @@ -51,9 +51,9 @@ position: fixed; top: 50%; left: 50%; - width: 100%; transform: translate(-50%, -50%); - max-width: min(100vw - 48px, 320px); + width: 320px; + max-width: calc(100vw - 48px); margin-top: -2rem; padding: 1.5rem; border-radius: 0.5rem; diff --git a/docs/src/app/new/(content)/components/dialog/demos/hero/tailwind/index.tsx b/docs/src/app/new/(content)/components/dialog/demos/hero/tailwind/index.tsx index 8ba5fe55b7..6006689d41 100644 --- a/docs/src/app/new/(content)/components/dialog/demos/hero/tailwind/index.tsx +++ b/docs/src/app/new/(content)/components/dialog/demos/hero/tailwind/index.tsx @@ -4,18 +4,18 @@ import { Dialog } from '@base-ui-components/react/Dialog'; export default function ExampleDialog() { return ( - + View notifications - + Your notifications You are all caught up. Good job! - + Close diff --git a/docs/src/app/new/(content)/layout.css b/docs/src/app/new/(content)/layout.css new file mode 100644 index 0000000000..226152f4cf --- /dev/null +++ b/docs/src/app/new/(content)/layout.css @@ -0,0 +1,37 @@ +@import 'docs/src/breakpoints.css'; + +@layer components { + .SidebarLayoutRoot { + --sidebar-width: 17.5rem; + + display: grid; + align-items: start; + padding: 0 3rem; + grid-template-columns: 1fr; + + @media (--show-side-nav) { + padding: 0; + grid-template-columns: var(--sidebar-width) 1fr; + } + + @media (--show-quick-nav) { + grid-template-columns: var(--sidebar-width) 1fr var(--sidebar-width); + } + } + + .SidebarLayoutMain { + padding-top: 3rem; + padding-bottom: 5rem; + min-width: 0; + margin: 0 auto; + + @media (--show-side-nav) { + margin-left: 0; + margin-right: 3rem; + } + + @media (--show-quick-nav) { + margin: 0; + } + } +} diff --git a/docs/src/app/new/(content)/layout.tsx b/docs/src/app/new/(content)/layout.tsx index 1d2b49c624..9cba193684 100644 --- a/docs/src/app/new/(content)/layout.tsx +++ b/docs/src/app/new/(content)/layout.tsx @@ -1,9 +1,163 @@ import * as React from 'react'; +import * as SideNav from 'docs/src/components/SideNav'; +import * as QuickNav from 'docs/src/components/quick-nav/QuickNav'; +import './layout.css'; export default function Layout({ children }: React.PropsWithChildren) { return ( -
-
{children}
+
+ + {nav.map((section) => ( + + {section.label} + + {section.links.map((link) => ( + + {link.label} + + ))} + + + ))} + + +
+ {children} +
); } + +const nav = [ + { + label: 'Overview', + links: [ + { + label: 'Quick start', + href: '/new/overview/quick-start', + }, + { + label: 'Accessibility', + href: '/new/overview/accessibility', + }, + { + label: 'Releases', + href: '/new/overview/releases', + }, + { + label: 'About', + href: '/new/overview/about', + }, + ], + }, + { + label: 'Handbook', + links: [ + { + label: 'Styling', + href: '/new/handbook/styling', + }, + { + label: 'Animation', + href: '/new/handbook/animation', + }, + { + label: 'Composition', + href: '/new/handbook/composition', + }, + { + label: 'Migrating from Radix', + href: '/new/handbook/migrating-from-radix', + }, + ], + }, + { + label: 'Components', + links: [ + { + label: 'Alert Dialog', + href: '/new/components/alert-dialog', + }, + { + label: 'Checkbox', + href: '/new/components/checkbox', + }, + { + label: 'Checkbox Group', + href: '/new/components/checkbox group', + }, + { + label: 'Collapsible', + href: '/new/components/collapsible', + }, + { + label: 'Combobox', + href: '/new/components/combobox', + }, + { + label: 'Datepicker', + href: '/new/components/datepicker', + }, + { + label: 'Dialog', + href: '/new/components/dialog', + }, + { + label: 'Field', + href: '/new/components/field', + }, + { + label: 'Fieldset', + href: '/new/components/fieldset', + }, + { + label: 'Form', + href: '/new/components/form', + }, + { + label: 'Menu', + href: '/new/components/menu', + }, + { + label: 'Number Field', + href: '/new/components/number-field', + }, + { + label: 'Popover', + href: '/new/components/popover', + }, + { + label: 'Preview Card', + href: '/new/components/preview-card', + }, + { + label: 'Progress', + href: '/new/components/progress', + }, + { + label: 'Radio Group', + href: '/new/components/radio-group', + }, + { + label: 'Separator', + href: '/new/components/separator', + }, + { + label: 'Slider', + href: '/new/components/slider', + }, + { + label: 'Switch', + href: '/new/components/switch', + }, + { + label: 'Tabs', + href: '/new/components/tabs', + }, + { + label: 'Tooltip', + href: '/new/components/tooltip', + }, + ], + }, +]; diff --git a/docs/src/app/new/layout.css b/docs/src/app/new/layout.css index 1b89665988..3eacf403e5 100644 --- a/docs/src/app/new/layout.css +++ b/docs/src/app/new/layout.css @@ -1,17 +1,90 @@ -html { - color-scheme: light dark; +@import 'docs/src/breakpoints.css'; - /* macOS overscroll background */ - background-color: #000; -} +@layer base { + html { + color-scheme: light dark; + + /* macOS overscroll background */ + background-color: var(--color-gray-50); + } + + body { + min-width: 360px; + line-height: 1.5; + background-color: var(--color-background); + color: var(--color-foreground); + } -body { - min-width: 360px; - line-height: 1.5; - background-color: var(--color-background); - color: var(--color-foreground); + ::selection { + background-color: var(--color-selection); + } } -::selection { - background-color: var(--color-selection); +@layer components { + .RootLayout { + /* Padding starts with 2rem, and climbs up to 3rem between 640px and 960px */ + --root-layout-padding-x: round(clamp(2rem, 5vw, 3rem), 1px); + + z-index: 0; + position: relative; + padding-inline: var(--root-layout-padding-x); + + &::before, + &::after { + content: ''; + position: absolute; + background-color: var(--color-gridline); + height: 1px; + right: 0; + left: 0; + } + + &::before { + top: 3rem; + margin-top: -1px; + } + + &::after { + bottom: 3rem; + margin-bottom: -1px; + } + } + + .RootLayoutContainer { + position: relative; + display: flex; + margin-inline: auto; + min-height: 100dvh; + max-width: calc(var(--breakpoint-max-layout-width) - var(--root-layout-padding-x) * 2); + flex-direction: column; + padding-top: 3rem; + padding-bottom: 3rem; + + &::before, + &::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + width: 1px; + background-color: var(--color-gridline); + } + + &::before { + left: 0; + margin-left: -1px; + } + + &::after { + right: 0; + margin-right: -1px; + } + } + + .RootLayoutContent { + display: flex; + flex-grow: 1; + flex-direction: column; + background-color: var(--color-content); + } } diff --git a/docs/src/app/new/layout.tsx b/docs/src/app/new/layout.tsx index aeecaecd77..775b63c77a 100644 --- a/docs/src/app/new/layout.tsx +++ b/docs/src/app/new/layout.tsx @@ -4,13 +4,9 @@ import './layout.css'; export default function Layout({ children }: React.PropsWithChildren) { return ( -
- - -
- - -
{children}
+
+
+
{children}
); diff --git a/docs/src/breakpoints.css b/docs/src/breakpoints.css new file mode 100644 index 0000000000..0c322b2258 --- /dev/null +++ b/docs/src/breakpoints.css @@ -0,0 +1,21 @@ +@theme { + /* Default Tailwind breakpoints */ + --breakpoint-sm: 40rem; /* 640px */ + --breakpoint-md: 48rem; /* 768px */ + --breakpoint-lg: 64rem; /* 1024px */ + --breakpoint-xl: 80rem; /* 1280px */ + --breakpoint-2xl: 96rem; /* 1536px */ + + /* Functional breakpoints */ + --breakpoint-show-side-nav: 64rem; /* 1024px */ + --breakpoint-show-quick-nav: 84rem; /* 1344px */ + --breakpoint-max-layout-width: 89rem; /* 1424px - this is a 1440px device minus 16px scrollbar */ +} + +@custom-media --sm (min-width: 40rem); +@custom-media --md (min-width: 48rem); +@custom-media --lg (min-width: 64rem); +@custom-media --xl (min-width: 80rem); +@custom-media --2xl (min-width: 96rem); +@custom-media --show-side-nav (min-width: 64rem); +@custom-media --show-quick-nav (min-width: 84rem); diff --git a/docs/src/components/Select.css b/docs/src/components/Select.css index f8299e887e..6470637193 100644 --- a/docs/src/components/Select.css +++ b/docs/src/components/Select.css @@ -2,6 +2,7 @@ .SelectPopup { transition-duration: 0ms; cursor: default; + -webkit-user-select: none; user-select: none; padding: 0.25rem; } diff --git a/docs/src/components/SideNav.css b/docs/src/components/SideNav.css new file mode 100644 index 0000000000..ea5c1dac65 --- /dev/null +++ b/docs/src/components/SideNav.css @@ -0,0 +1,132 @@ +@layer components { + .SideNavRoot { + --side-nav-item-height: 2rem; + --side-nav-item-line-height: var(--text-sm--line-height); + --side-nav-item-padding-y: calc( + var(--side-nav-item-height) / 2 - var(--side-nav-item-line-height) / 2 + ); + + --side-nav-scrollbar-thumb-width: 0.25rem; + --side-nav-scrollbar-width: 1.5rem; + --side-nav-scrollbar-gap-left: 1rem; + --side-nav-scrollbar-gap-right: 2.5rem; + margin-right: calc( + var(--side-nav-scrollbar-gap-right) - var(--side-nav-scrollbar-width) / 2 + + var(--side-nav-scrollbar-thumb-width) / 2 + ); + + @apply text-sm; + position: sticky; + top: 0; + + display: none; + @media (--show-side-nav) { + display: block; + } + } + + .SideNavViewport { + max-height: 100vh; + padding-top: 0.75rem; + padding-bottom: 3rem; + padding-left: 1.5rem; + padding-right: calc( + var(--side-nav-scrollbar-gap-left) + var(--side-nav-scrollbar-width) / 2 + + var(--side-nav-scrollbar-thumb-width) / 2 + ); + + /* Scroll containers are focusable */ + outline: 0; + .SideNavRoot:has(&:focus-visible)::before { + content: ''; + inset: 0; + pointer-events: none; + position: absolute; + outline: 2px solid var(--color-blue); + outline-offset: -2px; + /* Don't inset the outline on the right */ + right: -2px; + } + } + + .SideNavScrollbar { + display: flex; + padding-top: 1.5rem; + padding-bottom: 3rem; + + /* Click target width */ + width: var(--side-nav-scrollbar-width); + + opacity: 0; + transition: opacity 200ms 500ms; + + &:active, + &[data-scrolling], + .SideNavViewport:focus-visible + & { + transition-duration: 0ms; + transition-delay: 0ms; + opacity: 1; + } + } + + .SideNavScrollbarThumb { + display: flex; + justify-content: center; + width: 100%; + + &::before { + content: ''; + display: block; + height: 100%; + /* Visible thumb width */ + width: var(--side-nav-scrollbar-thumb-width); + border-radius: var(--radius-sm); + background-color: var(--color-gray-300); + } + } + + .SideNavSection { + margin-bottom: 1rem; + } + + .SideNavHeading { + display: inline-flex; + padding-block: var(--side-nav-item-padding-y); + font-weight: 500; + } + + .SideNavItem { + display: flex; + } + + .SideNavLink { + display: block; + padding: var(--side-nav-item-padding-y) 0.75rem; + border-radius: var(--radius-md); + + @media (hover: hover) { + &:hover { + text-decoration: underline; + text-decoration-color: var(--color-gray-500); + text-decoration-thickness: 1px; + text-underline-offset: 2px; + } + } + + &[data-active] { + flex-grow: 1; + background-color: var(--color-gray-50); + outline: 1px solid var(--color-gray-200); + outline-offset: -1px; + text-decoration: none; + font-weight: 500; + cursor: default; + } + + &:focus-visible { + z-index: 1; + outline: 2px solid var(--color-blue); + outline-offset: -1px; + } + } +} diff --git a/docs/src/components/SideNav.tsx b/docs/src/components/SideNav.tsx new file mode 100644 index 0000000000..67af9232c2 --- /dev/null +++ b/docs/src/components/SideNav.tsx @@ -0,0 +1,130 @@ +'use client'; +import * as React from 'react'; +import clsx from 'clsx'; +import NextLink from 'next/link'; +import { usePathname } from 'next/navigation'; +import { ScrollArea } from '@base-ui-components/react/ScrollArea'; +import scrollIntoView from 'scroll-into-view-if-needed'; +// eslint-disable-next-line no-restricted-imports +import { useEnhancedEffect } from '@base-ui-components/react/utils/useEnhancedEffect'; + +interface SideNavContextValue { + /** + * Whether we are programmatically scrolling an item into view. + * We make sure that the scrollbar is visible only during user interaction. + */ + scrollingIntoView: boolean; + setScrollingIntoView: (value: boolean) => void; +} + +const SideNavContext = React.createContext({ + scrollingIntoView: false, + setScrollingIntoView: () => undefined, +}); + +export function Root({ children, className, ...props }: React.ComponentProps<'div'>) { + const [scrollingIntoView, setScrollingIntoView] = React.useState(false); + const contextValue = React.useMemo( + () => ({ scrollingIntoView, setScrollingIntoView }), + [scrollingIntoView, setScrollingIntoView], + ); + + return ( + + + + ); +} + +function Scrollbar(props: React.ComponentProps<'div'>) { + const { scrollingIntoView, setScrollingIntoView } = React.useContext(SideNavContext); + const dataScrolling = (props as Record)['data-scrolling']; + const prevScrolling = React.useRef(dataScrolling); + + useEnhancedEffect(() => { + // Clear `scrollingIntoView` state when the ScrollArea's + // `state.scrolling` flips back to `false` + if (prevScrolling.current && !dataScrolling && scrollingIntoView) { + setScrollingIntoView(false); + } + prevScrolling.current = dataScrolling; + }, [scrollingIntoView, setScrollingIntoView, dataScrolling]); + + return ( + + + + ); +} + +export function Section({ className, ...props }: React.ComponentProps<'div'>) { + return
; +} + +export function Heading({ className, ...props }: React.ComponentProps<'div'>) { + return
; +} + +export function List({ className, ...props }: React.ComponentProps<'ul'>) { + return
    ; +} + +interface ItemProps extends React.ComponentProps<'li'> { + active?: boolean; + href: string; +} + +export function Item({ children, className, href, ...props }: ItemProps) { + const { setScrollingIntoView } = React.useContext(SideNavContext); + const ref = React.useRef(null); + const pathname = usePathname(); + const active = pathname === href; + + React.useEffect(() => { + if (ref.current && active) { + // TODO Vlad this should be rem, not 48px + const HEADER_HEIGHT = 48; + const SCROLL_MARGIN = 48; + const viewport = document.querySelector('[data-side-nav-viewport]'); + + if (!viewport) { + return; + } + + scrollIntoView(ref.current, { + block: 'nearest', + scrollMode: 'if-needed', + boundary: (parent) => viewport.contains(parent), + behavior: (actions) => { + // We are scrolling into view, update upstream state + setScrollingIntoView(true); + actions.forEach(({ top }) => { + const dir = viewport.scrollTop > top ? -1 : 1; + const offset = Math.max(0, HEADER_HEIGHT - Math.max(0, window.scrollY)); + viewport.scrollTop = top + offset + SCROLL_MARGIN * dir; + }); + }, + }); + } + }, [active, setScrollingIntoView]); + + return ( +
  • + + {children} + +
  • + ); +} diff --git a/docs/src/components/demo/Demo.tsx b/docs/src/components/demo/Demo.tsx index c418767066..4ddf850204 100644 --- a/docs/src/components/demo/Demo.tsx +++ b/docs/src/components/demo/Demo.tsx @@ -45,7 +45,7 @@ export function Demo({ className, defaultOpen = false, title, ...props }: DemoPr }} > Copy - + {copyTimeout ? : } diff --git a/docs/src/components/demo/index.css b/docs/src/components/demo/index.css index d168dde268..675a9a1d65 100644 --- a/docs/src/components/demo/index.css +++ b/docs/src/components/demo/index.css @@ -163,6 +163,7 @@ & pre { @apply text-xs; padding: 0.5rem 0.75rem; + display: flex; cursor: text; /* Closed state */ @@ -199,7 +200,6 @@ max-height: none; /* Scroll */ - display: flex; overflow: auto; overscroll-behavior-x: contain; scrollbar-width: thin; diff --git a/docs/src/components/quick-nav/QuickNav.css b/docs/src/components/quick-nav/QuickNav.css index 08031bffb1..8f75b634e2 100644 --- a/docs/src/components/quick-nav/QuickNav.css +++ b/docs/src/components/quick-nav/QuickNav.css @@ -1,5 +1,18 @@ @layer components { + .QuickNavContainer { + /* Quick Nav's container shouldn't have any paddings to maintain correct positioning */ + padding: 0; + position: relative; + } + .QuickNavRoot { + --quick-nav-margin-x: 2rem; + --quick-nav-item-height: 2rem; + --quick-nav-item-line-height: var(--text-sm--line-height); + --quick-nav-item-padding-y: calc( + var(--quick-nav-item-height) / 2 - var(--quick-nav-item-line-height) / 2 + ); + /* The variable is used in JS positioning logic */ --top: -1px; @apply text-sm; @@ -11,7 +24,7 @@ contain: layout; display: none; - @media (min-width: 1340px) { + @media (--show-quick-nav) { display: block; } } @@ -19,17 +32,15 @@ .QuickNavInner { border-top: 1px solid var(--color-gray-200); position: relative; - left: 2rem; - padding-top: 1rem; + left: var(--quick-nav-margin-x); + padding-top: 0.75rem; padding-bottom: 1.5rem; pointer-events: auto; - - /* Stretch width between 168px at 1340px and up to 216px at 1440px */ - width: max(168px, min(216px, 168px + (216 - 168) * (100vw - 1340px) / 100)); + width: calc(var(--sidebar-width) - var(--quick-nav-margin-x) * 2); } .QuickNavTitle { - padding-block: 0.375rem; + padding-block: var(--quick-nav-item-padding-y); font-weight: 500; } @@ -46,8 +57,8 @@ .QuickNavLink { display: flex; - padding: 0.375rem; - margin-inline: -0.375rem; + padding: var(--quick-nav-item-padding-y) 0.5rem; + margin-inline: -0.5rem; &:focus-visible { color: var(--color-foreground); @@ -58,7 +69,10 @@ @media (hover: hover) { &:hover { - color: var(--color-blue); + text-decoration: underline; + text-underline-offset: 2px; + text-decoration-thickness: 1px; + text-decoration-color: var(--color-gray-500); } } } diff --git a/docs/src/components/quick-nav/QuickNav.tsx b/docs/src/components/quick-nav/QuickNav.tsx index 9fe6becd47..75f15eab10 100644 --- a/docs/src/components/quick-nav/QuickNav.tsx +++ b/docs/src/components/quick-nav/QuickNav.tsx @@ -2,13 +2,17 @@ import * as React from 'react'; import clsx from 'clsx'; +export function Container({ className, ...props }: React.ComponentProps<'div'>) { + return
    ; +} + export function Root({ children, className, ...props }: React.ComponentProps<'div'>) { const ref = React.useRef(null); React.useEffect(() => onMounted(ref), []); return ( -
    +
    + ); } @@ -247,7 +251,7 @@ function onMounted(ref: React.RefObject) { } export function Title({ className, ...props }: React.ComponentProps<'h2'>) { - return

    ; + return
    ; } export function List({ className, ...props }: React.ComponentProps<'ul'>) { diff --git a/docs/src/styles.css b/docs/src/styles.css index 7d5bd5fb22..962cbc462b 100644 --- a/docs/src/styles.css +++ b/docs/src/styles.css @@ -7,6 +7,7 @@ @import 'tailwindcss/theme' layer(theme); @import 'tailwindcss/utilities' layer(utilities); +@import './breakpoints.css'; @import './fonts/index.css'; @import './syntax-highlighting/index.css'; @import './components/demo/index.css'; @@ -16,6 +17,7 @@ @import './components/GhostButton.css'; @import './components/Popup.css'; @import './components/Select.css'; +@import './components/SideNav.css'; @import './components/Table.css'; /* Handy link to the default Tailwind theme: @@ -43,7 +45,7 @@ /* Functional colors */ --color-content: white; --color-background: var(--color-gray-50); - --color-foreground: var(--color-gray-950); + --color-foreground: var(--color-gray-900); --color-popup: white; --color-gridline: oklch(91.6% 1% 264); --color-highlight: var(--color-blue); @@ -67,7 +69,7 @@ --text-xs--tracking: 0.0015em; --text-sm: 0.9375rem; - --text-sm--line-height: 1.25rem; + --text-sm--line-height: 1.375rem; --text-sm--tracking: 0.001em; --text-base: 1rem; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d82ba76c99..7704705041 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -461,6 +461,9 @@ importers: postcss: specifier: ^8.4.47 version: 8.4.47 + postcss-custom-media: + specifier: ^11.0.5 + version: 11.0.5(postcss@8.4.47) postcss-import: specifier: ^16.1.0 version: 16.1.0(postcss@8.4.47) @@ -509,6 +512,9 @@ importers: remark-rehype: specifier: ^11.1.1 version: 11.1.1 + scroll-into-view-if-needed: + specifier: 3.1.0 + version: 3.1.0 shiki: specifier: ^1.22.2 version: 1.22.2 @@ -1448,16 +1454,33 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@csstools/cascade-layer-name-parser@2.0.4': + resolution: {integrity: sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-parser-algorithms@2.6.1': resolution: {integrity: sha512-ubEkAaTfVZa+WwGhs5jbo5Xfqpeaybr/RvWzvFxRs4jfq16wH8l8Ty/QEEpINxll4xhuGfdMbipRyz5QZh9+FA==} engines: {node: ^14 || ^16 || >=18} peerDependencies: '@csstools/css-tokenizer': ^2.2.4 + '@csstools/css-parser-algorithms@3.0.4': + resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.3 + '@csstools/css-tokenizer@2.2.4': resolution: {integrity: sha512-PuWRAewQLbDhGeTvFuq2oClaSCKPIBmHyIobCV39JHRYN0byDcUWJl5baPeNUcqrjtdMNqFooE0FGl31I3JOqw==} engines: {node: ^14 || ^16 || >=18} + '@csstools/css-tokenizer@3.0.3': + resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + engines: {node: '>=18'} + '@csstools/media-query-list-parser@2.1.9': resolution: {integrity: sha512-qqGuFfbn4rUmyOB0u8CVISIp5FfJ5GAR3mBrZ9/TKndHakdnm6pY0L/fbLcpPnrzwCyyTEZl1nUcXAYHEWneTA==} engines: {node: ^14 || ^16 || >=18} @@ -1465,6 +1488,13 @@ packages: '@csstools/css-parser-algorithms': ^2.6.1 '@csstools/css-tokenizer': ^2.2.4 + '@csstools/media-query-list-parser@4.0.2': + resolution: {integrity: sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + '@csstools/selector-specificity@3.0.3': resolution: {integrity: sha512-KEPNw4+WW5AVEIyzC80rTbWEUatTW2lXpN8+8ILC8PiPeWPjwUzrPZDIOZ2wwqDmeqOYTdSGyL3+vE5GC3FB3Q==} engines: {node: ^14 || ^16 || >=18} @@ -4208,6 +4238,9 @@ packages: resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} engines: {node: '>= 0.8.0'} + compute-scroll-into-view@3.1.0: + resolution: {integrity: sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -7653,6 +7686,12 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} + postcss-custom-media@11.0.5: + resolution: {integrity: sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + postcss-import@16.1.0: resolution: {integrity: sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==} engines: {node: '>=18.0.0'} @@ -8324,6 +8363,9 @@ packages: resolution: {integrity: sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==} engines: {node: '>= 12.13.0'} + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -10480,17 +10522,33 @@ snapshots: '@colors/colors@1.5.0': {} + '@csstools/cascade-layer-name-parser@2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/css-parser-algorithms@2.6.1(@csstools/css-tokenizer@2.2.4)': dependencies: '@csstools/css-tokenizer': 2.2.4 + '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-tokenizer': 3.0.3 + '@csstools/css-tokenizer@2.2.4': {} + '@csstools/css-tokenizer@3.0.3': {} + '@csstools/media-query-list-parser@2.1.9(@csstools/css-parser-algorithms@2.6.1(@csstools/css-tokenizer@2.2.4))(@csstools/css-tokenizer@2.2.4)': dependencies: '@csstools/css-parser-algorithms': 2.6.1(@csstools/css-tokenizer@2.2.4) '@csstools/css-tokenizer': 2.2.4 + '@csstools/media-query-list-parser@4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/selector-specificity@3.0.3(postcss-selector-parser@6.0.16)': dependencies: postcss-selector-parser: 6.0.16 @@ -13572,6 +13630,8 @@ snapshots: transitivePeerDependencies: - supports-color + compute-scroll-into-view@3.1.0: {} + concat-map@0.0.1: {} concat-stream@2.0.0: @@ -17988,6 +18048,14 @@ snapshots: possible-typed-array-names@1.0.0: {} + postcss-custom-media@11.0.5(postcss@8.4.47): + dependencies: + '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + postcss: 8.4.47 + postcss-import@16.1.0(postcss@8.4.47): dependencies: postcss: 8.4.47 @@ -18705,6 +18773,10 @@ snapshots: ajv-formats: 2.1.1(ajv@8.12.0) ajv-keywords: 5.1.0(ajv@8.12.0) + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.0 + semver@5.7.2: {} semver@6.3.1: {}