From 192a8a93a0ed69f6c5fdb32d8def879046a0faf4 Mon Sep 17 00:00:00 2001 From: Vlad Moroz Date: Thu, 28 Nov 2024 16:02:33 +0100 Subject: [PATCH] [docs] Mobile nav (#881) --- docs/src/app/layout.tsx | 2 +- .../demos/hero/css-modules/index.module.css | 6 +- .../dialog/demos/hero/tailwind/index.tsx | 6 +- docs/src/app/new/(content)/layout.css | 8 +- docs/src/app/new/(content)/layout.tsx | 134 +--------- docs/src/app/new/layout.css | 42 ++- docs/src/components/Header.css | 60 ++++- docs/src/components/Header.tsx | 83 +++++- docs/src/components/MobileNav.css | 249 ++++++++++++++++++ docs/src/components/MobileNav.tsx | 153 +++++++++++ docs/src/components/Popup.css | 12 +- docs/src/components/SideNav.css | 1 + docs/src/components/SideNav.tsx | 17 +- docs/src/components/quick-nav/QuickNav.css | 2 + .../reference/ReferenceTablePopover.tsx | 2 +- docs/src/icons/Npm.tsx | 16 ++ docs/src/mdx-components.tsx | 16 +- docs/src/nav.ts | 125 +++++++++ docs/src/styles.css | 10 +- 19 files changed, 739 insertions(+), 205 deletions(-) create mode 100644 docs/src/components/MobileNav.css create mode 100644 docs/src/components/MobileNav.tsx create mode 100644 docs/src/icons/Npm.tsx create mode 100644 docs/src/nav.ts diff --git a/docs/src/app/layout.tsx b/docs/src/app/layout.tsx index 23def0a3e0..8389157842 100644 --- a/docs/src/app/layout.tsx +++ b/docs/src/app/layout.tsx @@ -19,7 +19,7 @@ export default function Layout({ children }: React.PropsWithChildren) { /> 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 6e30e50b6d..138eb292f9 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 @@ -22,6 +22,10 @@ background-color: var(--color-gray-100); } } + + &:active { + background-color: var(--color-gray-100); + } } .Close { @@ -34,7 +38,7 @@ inset: 0; background-color: black; opacity: 0.2; - transition: opacity 150ms ease-out; + transition: opacity 150ms cubic-bezier(0.45, 1.005, 0, 1.005); @media (prefers-color-scheme: dark) { opacity: 0.7; 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 97878a16bf..0645d4f6f0 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,10 +4,10 @@ import { Dialog } from '@base-ui-components/react/dialog'; export default function ExampleDialog() { return ( - + View notifications - + Your notifications @@ -15,7 +15,7 @@ export default function ExampleDialog() { 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 index ba23cd32bf..4f31f85220 100644 --- a/docs/src/app/new/(content)/layout.css +++ b/docs/src/app/new/(content)/layout.css @@ -10,7 +10,7 @@ grid-template-columns: 1fr; @media (--sm) { - padding-inline: 3rem; + padding-inline: 2.5rem; } @media (--show-side-nav) { @@ -24,11 +24,13 @@ } .ContentLayoutMain { + min-width: 0; + max-width: 48rem; + width: 100%; + padding-top: 1.5rem; padding-bottom: 5rem; - min-width: 0; margin: 0 auto; - width: 100%; @media (--sm) { padding-top: 2rem; diff --git a/docs/src/app/new/(content)/layout.tsx b/docs/src/app/new/(content)/layout.tsx index cd7084c8ab..dc3e077d7a 100644 --- a/docs/src/app/new/(content)/layout.tsx +++ b/docs/src/app/new/(content)/layout.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import type { Metadata } from 'next/types'; import * as SideNav from 'docs/src/components/SideNav'; import * as QuickNav from 'docs/src/components/quick-nav/QuickNav'; +import { nav } from 'docs/src/nav'; import './layout.css'; export default function Layout({ children }: React.PropsWithChildren) { @@ -29,139 +30,6 @@ export default function Layout({ children }: React.PropsWithChildren) { ); } -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', - }, - ], - }, -]; // Title and description are pulled from

and in the MDX. export const metadata: Metadata = { title: null, diff --git a/docs/src/app/new/layout.css b/docs/src/app/new/layout.css index 7fc1e43fc3..a7356aa5bf 100644 --- a/docs/src/app/new/layout.css +++ b/docs/src/app/new/layout.css @@ -28,32 +28,28 @@ --root-layout-padding-x: 0rem; padding-inline: var(--root-layout-padding-x); - @media (--sm) { - --root-layout-padding-x: 2rem; - } - @media (--show-side-nav) { --root-layout-padding-x: 3rem; - } - &::before, - &::after { - content: ''; - position: absolute; - background-color: var(--color-gridline); - height: 1px; - right: 0; - left: 0; - } + &::before, + &::after { + content: ''; + position: absolute; + background-color: var(--color-gridline); + height: 1px; + right: 0; + left: 0; + } - &::before { - top: 3rem; - margin-top: -1px; - } + &::before { + top: var(--header-height); + margin-top: -1px; + } - &::after { - bottom: 3rem; - margin-bottom: -1px; + &::after { + bottom: var(--header-height); + margin-bottom: -1px; + } } } @@ -64,9 +60,9 @@ min-height: 100dvh; max-width: calc(var(--breakpoint-max-layout-width) - var(--root-layout-padding-x) * 2); flex-direction: column; - padding-bottom: 3rem; + padding-bottom: var(--header-height); - @media (--sm) { + @media (--show-side-nav) { &::before, &::after { content: ''; diff --git a/docs/src/components/Header.css b/docs/src/components/Header.css index a95c3250f3..485a4d94a0 100644 --- a/docs/src/components/Header.css +++ b/docs/src/components/Header.css @@ -1,27 +1,43 @@ @layer components { .Header { @apply text-sm; + height: var(--header-height); + width: 100%; + } + + .HeaderInner { + height: inherit; display: flex; align-items: center; justify-content: space-between; padding-inline: 1.5rem; - height: 3rem; + + position: fixed; + top: 0; + inset-inline: 0; + box-shadow: inset 0 -1px var(--color-gridline); + background-color: var(--color-gray-50); + z-index: 1; + + @media (--show-side-nav) { + position: static; + box-shadow: none; + background-color: transparent; + } } - .HeaderLink { + .HeaderLink, + .HeaderButton { display: flex; align-items: center; gap: 0.375rem; - padding: 0.25rem; - margin: -0.25rem; + padding: 0.25rem 0.5rem; + margin: -0.25rem -0.5rem; 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; + @media not (hover: hover) { + &:active { + color: var(--color-gray-500); } } @@ -35,4 +51,28 @@ flex-shrink: 0; } } + + .HeaderButton { + @media (hover: hover) { + &:hover { + background-color: var(--color-gray-100); + } + } + @media not (hover: hover) { + &:active { + background-color: var(--color-gray-100); + } + } + } + + .HeaderLink { + @media (hover: hover) { + &:hover { + text-decoration: underline; + text-decoration-color: var(--color-gray-500); + text-decoration-thickness: 1px; + text-underline-offset: 2px; + } + } + } } diff --git a/docs/src/components/Header.tsx b/docs/src/components/Header.tsx index 191951bd44..4ad9a8d6c0 100644 --- a/docs/src/components/Header.tsx +++ b/docs/src/components/Header.tsx @@ -1,19 +1,82 @@ import * as React from 'react'; import NextLink from 'next/link'; -import { GitHubIcon } from '../icons/GitHub'; +import { GitHubIcon } from 'docs/src/icons/GitHub'; +import * as MobileNav from './MobileNav'; +import { nav } from '../nav'; +import { NpmIcon } from '../icons/Npm'; + +const VERSION = 'v1.0.0-alpha-1'; +export const HEADER_HEIGHT = 48; export function Header() { return (
- - Base UI - -
- v1.0.0-alpha.1 - - - GitHub - +
+ + base ui + + + + +
+ + +
+
+
+
+ Navigation + + + + {nav.map((section) => ( + + {section.label} + + {section.links.map((link) => ( + + {link.label} + + ))} + + + ))} + + + Resources + + + + + npm package + {VERSION} + + + + + GitHub + + + + + +
); diff --git a/docs/src/components/MobileNav.css b/docs/src/components/MobileNav.css new file mode 100644 index 0000000000..1d7e65d95d --- /dev/null +++ b/docs/src/components/MobileNav.css @@ -0,0 +1,249 @@ +@layer components { + .MobileNavBackdrop { + position: fixed; + inset: 0; + height: 100dvh; + + transition-duration: 600ms; + transition-property: -webkit-backdrop-filter, backdrop-filter, opacity; + transition-timing-function: var(--ease-out-fast); + -webkit-backdrop-filter: blur(1.5px); + backdrop-filter: blur(1.5px); + background-image: linear-gradient(to bottom, transparent 2rem, rgb(0 0 0 / 5%) 50%); + + @media (prefers-color-scheme: dark) { + background-image: linear-gradient(to bottom, transparent, rgb(0 0 0 / 25%) 6rem); + } + + &[data-entering], + &[data-exiting] { + -webkit-backdrop-filter: blur(0px); + backdrop-filter: blur(0px); + opacity: 0; + } + + &[data-exiting] { + transition-duration: 250ms; + transition-timing-function: var(--ease-in-slow); + } + } + + .MobileNavPopup { + /* TODO Vlad remove when the backdrop order bug is fixed */ + z-index: 1; + outline: 0; + + position: fixed; + inset: 0; + height: 100dvh; + + /* Half the transition duration for blur so the element sort of comes into the focus when it's halfway in */ + transition-duration: 600ms, 300ms; + transform-origin: top center; + transition-property: transform, filter; + transition-timing-function: var(--ease-out-fast); + + &[data-entering], + &[data-exiting] { + transform: translateY(100dvh); + filter: blur(1px); + } + + &[data-exiting] { + /* Delay the blur transition so it doesn't feel unfocused until halway out */ + transition-delay: 0ms, 125ms; + transition-duration: 250ms; + transition-timing-function: var(--ease-in-slow); + } + } + + .MobileNavViewport { + --mobile-nav-item-height: 2.5rem; + --mobile-nav-item-padding-x: 1.5rem; + + position: absolute; + inset: 0; + height: 100dvh; + overflow-y: auto; + + @apply text-base; + + /* Native apps don't show scrollbars on sheets like this */ + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } + + /* A decorative border between the status bar and the page */ + &::after { + content: ''; + position: fixed; + z-index: 1; + top: 0; + inset-inline: 0; + height: 1px; + transition: background-color 50ms; + } + &[data-clipped]::after { + background-color: var(--color-gridline); + } + } + + .MobileNavViewportInner { + position: relative; + + /* Prevent children's margin collapse */ + display: flex; + flex-direction: column; + } + + .MobileNavBackdropTapArea { + position: absolute; + inset: 0; + } + + .MobileNavPanel { + margin-top: var(--header-height); + position: relative; + padding-block: 1rem; + + border-top-left-radius: var(--radius-xl); + border-top-right-radius: var(--radius-xl); + + box-shadow: + 0 10px 64px -10px rgba(35, 39, 52, 0.2), + 0 0.25px 0 1px var(--color-gray-200); + + @media (prefers-color-scheme: dark) { + box-shadow: 0 0 0 1px var(--color-gray-200); + } + + /* Make bottom overscroll edges soft; visible during extreme rubber band overscroll at the bottom */ + background-image: linear-gradient(to bottom, var(--color-popup) calc(100% - 2rem), transparent); + + /* Make the panel narrower on wider screens and lose the overscroll gradient hocus pocus for a bottom margin */ + @media (--sm) { + margin-block: 5rem; + margin-inline: auto; + width: calc(100% - 6rem); + max-width: 40rem; + background-image: none; + background-color: var(--color-popup); + border-radius: var(--radius-xl); + } + } + + /* Smoothly hides bottom overscroll edge */ + .MobileNavBottomOverscroll { + position: absolute; + inset: 0; + background-image: linear-gradient(to bottom, transparent 30%, var(--color-popup) 50%); + + @media (--sm) { + display: none; + } + } + + .MobileNavSection { + margin-bottom: 1rem; + } + + .MobileNavHeading { + @apply text-lg; + font-weight: 500; + padding-inline: var(--mobile-nav-item-padding-x); + + .MobileNavHeadingInner { + display: flex; + align-items: center; + height: var(--mobile-nav-item-height); + } + + &::after { + content: ''; + display: block; + margin-top: -1px; + border-bottom: 1px solid var(--color-gray-200); + } + } + + /* TODO Vlad should headings be just items? */ + .MobileNavItem { + /* + * Border is a separate element so that links + * span the entire screen width and have the + * tap-highlight-color applied edge to edge. + */ + &::after { + content: ''; + display: block; + margin-inline: var(--mobile-nav-item-padding-x); + border-bottom: 1px solid var(--color-gray-200); + } + } + + .MobileNavLink { + flex-grow: 1; + display: flex; + gap: 0.675rem; + align-items: center; + height: var(--mobile-nav-item-height); + padding-inline: var(--mobile-nav-item-padding-x); + + @supports not (-webkit-tap-highlight-color: black) { + &:active { + background-color: var(--color-gray-100); + } + } + @supports (-webkit-tap-highlight-color: black) { + /* Applying background-color on :active shows it when touching items while scrolling */ + /* This activates only on real link taps */ + -webkit-tap-highlight-color: var(--color-gray-300); + } + + &:focus-visible { + outline: 2px solid var(--color-blue); + outline-offset: -1px; + } + } +} + +.MobileNavCloseContainer { + position: sticky; + top: 0.75rem; + left: 100%; + margin-right: 0.75rem; + height: 0; + width: fit-content; +} + +.MobileNavClose { + display: flex; + align-items: center; + justify-content: center; + color: var(--color-gray-500); + outline: 0; + + /* Visible circle */ + width: 1.75rem; + height: 1.75rem; + border-radius: 100%; + background-color: var(--color-gray-200); + + /* Blur the dividers behind */ + -webkit-backdrop-filter: blur(1rem); + backdrop-filter: blur(1rem); + + &:focus-visible { + outline: 2px solid var(--color-blue); + outline-offset: -1px; + } + + /* Tap target */ + &::after { + content: ''; + width: 3rem; + height: 3rem; + position: absolute; + } +} diff --git a/docs/src/components/MobileNav.tsx b/docs/src/components/MobileNav.tsx new file mode 100644 index 0000000000..718de77f3b --- /dev/null +++ b/docs/src/components/MobileNav.tsx @@ -0,0 +1,153 @@ +'use client'; +import * as React from 'react'; +import clsx from 'clsx'; +import NextLink from 'next/link'; +import { Dialog } from '@base-ui-components/react/dialog'; +import { HEADER_HEIGHT } from './Header'; + +type MobileNavState = [boolean, (open: boolean) => void]; +const MobileNavState = React.createContext([false, () => undefined]); + +export function Root(props: Dialog.Root.Props) { + const state = React.useState(false); + const [open, setOpen] = state; + + return ( + + + + ); +} + +export const Trigger = Dialog.Trigger; + +export function Backdrop({ className, ...props }: Dialog.Backdrop.Props) { + return ; +} + +export function Popup({ children, className, ...props }: Dialog.Popup.Props) { + const [, setOpen] = React.useContext(MobileNavState); + const rem = React.useRef(16); + + React.useEffect(() => { + rem.current = parseFloat(getComputedStyle(document.documentElement).fontSize); + }, []); + + return ( + +
+
{ + const viewport = event.currentTarget; + if (viewport.scrollTop > (HEADER_HEIGHT * rem.current) / 16) { + viewport.setAttribute('data-clipped', ''); + } else { + viewport.removeAttribute('data-clipped'); + } + }} + onTouchStart={(event) => { + const viewport = event.currentTarget; + + // Consider flicks from scroll top only (iOS does the same with its sheets) + if (viewport.scrollTop <= 0) { + viewport.addEventListener( + 'touchend', + function handleTouchEnd() { + // If touch ended and we are overscrolling past a threshold... + if (viewport.scrollTop < -32) { + const y = viewport.scrollTop; + viewport.addEventListener( + 'scroll', + function handleNextScroll() { + // ...look at whether the system's intertia scrolling is continuing the motion + // in the same direction. If so, the flick is strong enough to close the dialog. + if (viewport.scrollTop < y) { + // It's gonna eventually bounce back to scrollTop 0. We need to counteract this + // a bit so that the close transition doesn't appear slower than it should. + viewport.style.translate = `0px -${y}px`; + viewport.style.transform = `400ms`; + setOpen(false); + + // Sometimes the first scroll event comes with the same scroll position + // If so, give it another chance, call ourselves recursively + } else if (viewport.scrollTop === y) { + viewport.addEventListener('scroll', handleNextScroll, { once: true }); + } + }, + { once: true }, + ); + } + }, + { once: true }, + ); + } + }} + > +
+ {/* We need the area behind the panel to close on tap but also to scroll the viewport. */} + } /> + + +
+
+ + ); +} + +export function Section({ className, ...props }: React.ComponentProps<'div'>) { + return
; +} + +export function Heading({ children, className, ...props }: React.ComponentProps<'div'>) { + return ( +
+
{children}
+
+ ); +} + +export function List({ className, ...props }: React.ComponentProps<'ul'>) { + return
    ; +} + +interface ItemProps extends React.ComponentPropsWithoutRef<'li'> { + active?: boolean; + href: string; + rel?: string; +} + +export function Item({ children, className, href, rel, ...props }: ItemProps) { + const [, setOpen] = React.useContext(MobileNavState); + return ( +
  • + setOpen(false)}> + {children} + +
  • + ); +} diff --git a/docs/src/components/Popup.css b/docs/src/components/Popup.css index 306a16b1f3..ca300121e5 100644 --- a/docs/src/components/Popup.css +++ b/docs/src/components/Popup.css @@ -1,7 +1,7 @@ @layer components { .Popup { - max-width: var(--available-width); - max-height: var(--available-height); + max-width: var(--available-width, none); + max-height: var(--available-height, none); border-radius: var(--radius-md); background-color: var(--color-popup); overflow: hidden; @@ -18,10 +18,10 @@ outline-offset: -1px; } - transform-origin: var(--transform-origin); - transition-duration: 150ms; + transform-origin: var(--transform-origin, center); + transition-duration: 120ms; transition-property: opacity, transform; - transition-timing-function: cubic-bezier(0.3, 1.065, 0.01, 0.975); + transition-timing-function: var(--ease-out-fast); &[data-entering], &[data-exiting] { @@ -32,7 +32,7 @@ } &[data-exiting] { - transition-duration: 275ms; + transition-timing-function: var(--ease-in-slow); } } } diff --git a/docs/src/components/SideNav.css b/docs/src/components/SideNav.css index ea5c1dac65..f655b2bc77 100644 --- a/docs/src/components/SideNav.css +++ b/docs/src/components/SideNav.css @@ -1,5 +1,6 @@ @layer components { .SideNavRoot { + /* Match quick nav spacing so side nav and quick nav are visually aligned */ --side-nav-item-height: 2rem; --side-nav-item-line-height: var(--text-sm--line-height); --side-nav-item-padding-y: calc( diff --git a/docs/src/components/SideNav.tsx b/docs/src/components/SideNav.tsx index 0f948a2bf0..c9f322bc3c 100644 --- a/docs/src/components/SideNav.tsx +++ b/docs/src/components/SideNav.tsx @@ -7,6 +7,7 @@ import { ScrollArea } from '@base-ui-components/react/scroll-area'; import scrollIntoView from 'scroll-into-view-if-needed'; // eslint-disable-next-line no-restricted-imports import { SCROLL_TIMEOUT } from '@base-ui-components/react/scroll-area/constants'; +import { HEADER_HEIGHT } from './Header'; interface SideNavContextValue { /** @@ -74,17 +75,23 @@ interface ItemProps extends React.ComponentProps<'li'> { href: string; } +const SCROLL_MARGIN = 48; + 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; + const rem = React.useRef(16); + + React.useEffect(() => { + rem.current = parseFloat(getComputedStyle(document.documentElement).fontSize); + }, []); React.useEffect(() => { if (ref.current && active) { - // TODO Vlad this should be rem, not 48px - const HEADER_HEIGHT = 48; - const SCROLL_MARGIN = 48; + const scrollMargin = (SCROLL_MARGIN * rem.current) / 16; + const headerHeight = (HEADER_HEIGHT * rem.current) / 16; const viewport = document.querySelector('[data-side-nav-viewport]'); if (!viewport) { @@ -104,8 +111,8 @@ export function Item({ children, className, href, ...props }: ItemProps) { } 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; + const offset = Math.max(0, headerHeight - Math.max(0, window.scrollY)); + viewport.scrollTop = top + offset + scrollMargin * dir; }); }, }); diff --git a/docs/src/components/quick-nav/QuickNav.css b/docs/src/components/quick-nav/QuickNav.css index 8f75b634e2..9a635512ea 100644 --- a/docs/src/components/quick-nav/QuickNav.css +++ b/docs/src/components/quick-nav/QuickNav.css @@ -7,6 +7,8 @@ .QuickNavRoot { --quick-nav-margin-x: 2rem; + + /* Use line height instead of fixed item height in case text breaks into multiple lines */ --quick-nav-item-height: 2rem; --quick-nav-item-line-height: var(--text-sm--line-height); --quick-nav-item-padding-y: calc( diff --git a/docs/src/components/reference/ReferenceTablePopover.tsx b/docs/src/components/reference/ReferenceTablePopover.tsx index 8ef7db3625..97c1750ad5 100644 --- a/docs/src/components/reference/ReferenceTablePopover.tsx +++ b/docs/src/components/reference/ReferenceTablePopover.tsx @@ -8,7 +8,7 @@ import { Popup } from '../Popup'; export function ReferenceTablePopover({ children }: React.PropsWithChildren) { const isMobile = useMediaQuery('@media (width < 48rem)', { noSsr: true }); return ( - + diff --git a/docs/src/icons/Npm.tsx b/docs/src/icons/Npm.tsx new file mode 100644 index 0000000000..fac114950c --- /dev/null +++ b/docs/src/icons/Npm.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; + +export function NpmIcon(props: React.ComponentProps<'svg'>) { + return ( + + + + ); +} diff --git a/docs/src/mdx-components.tsx b/docs/src/mdx-components.tsx index b1161ea2e3..b360b8f493 100644 --- a/docs/src/mdx-components.tsx +++ b/docs/src/mdx-components.tsx @@ -18,20 +18,22 @@ export const mdxComponents: MDXComponents = { code: (props) => , h1: (props) => ( -

    +

    {`${getChildrenText(props.children)} ยท Base UI`} ), h2: (props) => (
    -

    +

    ), - h3: (props) =>

    , - h4: (props) =>

    , - h5: (props) =>

    , - h6: (props) =>
    , + h3: (props) => ( +

    + ), + h4: (props) =>

    , + h5: (props) =>

    , + h6: (props) =>
    , p: (props) =>

    , figure: (props) => { if ('data-rehype-pretty-code-figure' in props) { @@ -66,7 +68,7 @@ export const mdxComponents: MDXComponents = { PropsTable: (props) => , Subtitle: (props) => ( -

    +

    ), diff --git a/docs/src/nav.ts b/docs/src/nav.ts new file mode 100644 index 0000000000..4aa91f8321 --- /dev/null +++ b/docs/src/nav.ts @@ -0,0 +1,125 @@ +export 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: '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/styles.css b/docs/src/styles.css index b854fc8c12..e6cb7c6b6e 100644 --- a/docs/src/styles.css +++ b/docs/src/styles.css @@ -15,6 +15,7 @@ @import './components/Code.css'; @import './components/CodeBlock.css'; @import './components/GhostButton.css'; +@import './components/MobileNav.css'; @import './components/Header.css'; @import './components/Popup.css'; @import './components/Select.css'; @@ -33,7 +34,7 @@ --color-gray-50: oklch(98% 0.25% 264); --color-gray-75: oklch(97% 0.325% 264); /* Not a Tailwind step, don't use it in the demos */ --color-gray-100: oklch(12% 9.5% 264 / 5%); - --color-gray-200: oklch(12% 9% 264 / 8%); + --color-gray-200: oklch(12% 9% 264 / 7%); --color-gray-300: oklch(12% 8.5% 264 / 17%); --color-gray-400: oklch(12% 8% 264 / 38%); --color-gray-500: oklch(12% 7.5% 264 / 50%); @@ -104,6 +105,11 @@ --text-6xl: 3.75rem; --text-6xl--line-height: 0.95; --text-6xl--letter-spacing: -0.015em; + + --default-transition-timing-function: var(--ease-out-fast); + --ease-out-fast: cubic-bezier(0.45, 1.005, 0, 1.005); + --ease-in-slow: cubic-bezier(0.375, 0.015, 0.545, 0.455); + --header-height: 3rem; } :root { @@ -111,7 +117,7 @@ --color-gray-50: oklch(17% 1% 264); --color-gray-75: oklch(19% 2% 264); /* Not a Tailwind step, don't use it in the demos */ --color-gray-100: oklch(28% 3% 264 / 65%); - --color-gray-200: oklch(31% 3% 264 / 80%); + --color-gray-200: oklch(29% 3% 264 / 80%); --color-gray-300: oklch(35% 3% 264 / 80%); --color-gray-400: oklch(47% 3.5% 264 / 80%); --color-gray-500: oklch(64% 4% 264 / 80%);