diff --git a/docs-ui/src/app/layout.module.css b/docs-ui/src/app/layout.module.css index 6664be49c9..5c1d113a98 100644 --- a/docs-ui/src/app/layout.module.css +++ b/docs-ui/src/app/layout.module.css @@ -1,27 +1,20 @@ .container { position: relative; z-index: 20; - width: calc(100% - 64px); - margin-inline: 0 16px; - margin-left: 32px; - margin-right: 32px; - margin-top: 112px; margin-bottom: 48px; - padding-inline: 40px; - padding-block: 48px; - border-radius: 0.5rem; + padding-inline: 24px; } .content { width: 100%; - max-width: 960px; + max-width: 1200px; margin: 0 auto; } @media (min-width: 768px) { .container { - width: calc(100% - 332px - 40px); - margin-left: 332px; + width: calc(100% - 260px); + margin-left: 260px; margin-right: 40px; } } diff --git a/docs-ui/src/app/layout.tsx b/docs-ui/src/app/layout.tsx index d197112431..2bafa97d32 100644 --- a/docs-ui/src/app/layout.tsx +++ b/docs-ui/src/app/layout.tsx @@ -1,5 +1,5 @@ import type { Metadata } from 'next'; -import { Sidebar } from '../components/Sidebar'; +import { Sidebar } from '@/components/Sidebar/Sidebar'; import { Toolbar } from '@/components/Toolbar'; import { StickyHeader } from '../components/StickyHeader/StickyHeader'; import { Providers } from './providers'; @@ -51,10 +51,12 @@ export default async function RootLayout({ - - + {/* */}
-
{children}
+
+ + {children} +
diff --git a/docs-ui/src/components/PageTitle/index.tsx b/docs-ui/src/components/PageTitle/index.tsx index 540e19eb36..aef3d75d70 100644 --- a/docs-ui/src/components/PageTitle/index.tsx +++ b/docs-ui/src/components/PageTitle/index.tsx @@ -13,7 +13,6 @@ export const PageTitle = ({ }) => { return (
-
{type}
{ return (
+
+ +
diff --git a/docs-ui/src/components/Toolbar/Toolbar.module.css b/docs-ui/src/components/Toolbar/Toolbar.module.css index f0e58f2edb..4566904df5 100644 --- a/docs-ui/src/components/Toolbar/Toolbar.module.css +++ b/docs-ui/src/components/Toolbar/Toolbar.module.css @@ -1,76 +1,59 @@ .toolbar { - position: fixed; display: flex; - top: 0; - left: 32px; - right: 32px; - height: 112px; - z-index: 10; + height: 80px; align-items: center; justify-content: space-between; - padding-left: 0.25rem; -} + margin-bottom: 24px; -.left { - /* width: 296px; */ - display: flex; - align-items: center; - gap: 0.5rem; - padding-right: 20px; -} - -.right { - flex: 1; - display: flex; - align-items: center; - justify-content: flex-end; -} - -.actions { - display: none; -} - -.version { - display: none; -} - -.versionLinks { - display: flex; - align-items: center; - - a { - width: 48px; - height: 48px; - display: flex; - align-items: center; - justify-content: center; - color: var(--secondary); - transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out; - border-radius: 48px; - - &:hover { - color: var(--primary); - background-color: var(--action); - } - } -} - -@media (min-width: 600px) { - .actions { - display: flex; - align-items: center; - gap: 1rem; - } -} - -@media (min-width: 768px) { - .toolbar { + @media (min-width: 768px) { right: 40px; } } +.breadcrumb { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + font-weight: 500; +} + +.breadcrumbLink { + color: var(--secondary); + text-decoration: none; + cursor: pointer; + + &:hover { + color: var(--primary); + text-decoration: underline; + transition: color 0.2s ease-in-out; + text-decoration-thickness: 1px; + text-underline-offset: 4px; + } +} + +.breadcrumbSeparator { + color: var(--secondary); + flex-shrink: 0; +} + +.breadcrumbCurrent { + color: var(--primary); +} + +.actions { + display: none; + + @media (min-width: 320px) { + display: flex; + display: flex; + align-items: center; + gap: 0.5rem; + } +} + @media (min-width: 820px) { - .right { + .content { justify-content: space-between; } @@ -79,18 +62,164 @@ align-items: center; justify-content: center; /* background-color: var(--action); */ - border: 1px solid var(--border2); - border-radius: 48px; - padding-inline: 20px; - color: var(--primary); - font-size: 0.875rem; - font-weight: 500; - height: 48px; } } -@media (min-width: 960px) { - .left { - width: 296px; +.bubble { + display: flex; + align-items: center; + justify-content: center; + background-color: var(--bg); + border: 1px solid var(--border2); + border-radius: 32px; + padding-inline: 16px; + color: var(--primary); + font-size: 0.875rem; + font-weight: 500; + height: 32px; + cursor: pointer; + gap: 4px; + + &[data-selected] { + background-color: var(--action); + color: var(--primary); + } + + &:hover { + background-color: var(--action); + transition: background-color 0.2s ease-in-out; + } + + &[data-hide-tablet] { + display: none; + } + + @media (min-width: 1024px) { + &[data-hide-tablet] { + display: flex; + } + } +} + +.buttonGroup { + display: flex; + align-items: center; + gap: 4px; + border: 1px solid var(--border2); + border-radius: 32px; + padding-inline: 4px; + height: 32px; + display: none; + + @media (min-width: 480px) { + display: flex; + } +} + +.buttonGroup button { + height: 24px; + background-color: var(--bg); + border: none; + border-radius: 28px; + padding-inline: 16px; + color: var(--primary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + + &[data-selected] { + background-color: var(--action); + color: var(--primary); + } + + &:hover { + background-color: var(--action); + transition: background-color 0.2s ease-in-out; + } +} + +.Popup { + box-sizing: border-box; + padding-block: 0.25rem; + border-radius: 0.375rem; + background-color: var(--bg); + color: var(--color-gray-900); + border: 1px solid var(--border); + padding-inline: 0.25rem; + transform-origin: var(--transform-origin); + transition: transform 150ms, opacity 150ms; + + &[data-starting-style], + &[data-ending-style] { + opacity: 0; + transform: scale(0.9); + } + + &[data-side='none'] { + transition: none; + transform: none; + opacity: 1; + } + + @media (prefers-color-scheme: light) { + outline: 1px solid var(--color-gray-200); + box-shadow: 0px 10px 15px -3px var(--color-gray-200), + 0px 4px 6px -4px var(--color-gray-200); + } + + @media (prefers-color-scheme: dark) { + outline: 1px solid var(--color-gray-300); + outline-offset: -1px; + } +} + +.Popup[data-trigger='Select'] { + min-width: var(--trigger-width); + + .ListBox { + display: block; + width: unset; + max-height: inherit; + min-height: unset; + border: none; + + .react-aria-Header { + padding-left: 1.571rem; + } + } + + .Item { + position: relative; + padding: 0 0.571rem 0 1.571rem; + height: 2rem; + display: flex; + align-items: center; + + &[data-focus-visible] { + outline: none; + } + + &[data-selected] { + font-weight: 600; + background: unset; + color: var(--text-color); + + &::before { + content: '✓'; + content: '✓' / ''; + alt: ' '; + position: absolute; + left: 4px; + } + } + + &[data-focused], + &[data-pressed] { + background: var(--bg); + color: var(--primary); + cursor: pointer; + } } } diff --git a/docs-ui/src/components/Toolbar/Toolbar.tsx b/docs-ui/src/components/Toolbar/Toolbar.tsx index 29570739f8..f0399797ea 100644 --- a/docs-ui/src/components/Toolbar/Toolbar.tsx +++ b/docs-ui/src/components/Toolbar/Toolbar.tsx @@ -1,52 +1,161 @@ 'use client'; -import { RiGithubLine, RiNpmjsLine } from '@remixicon/react'; -import { motion, useScroll, useTransform } from 'framer-motion'; -import { useRef } from 'react'; -import { Logo } from './Logo'; -import { ThemeSelector } from './theme'; -import { ThemeNameSelector } from './theme-name'; +import { + RiArrowDownSLine, + RiArrowRightSLine, + RiGithubLine, + RiMoonLine, + RiSunLine, +} from '@remixicon/react'; +import { + Button, + ListBox, + ListBoxItem, + Popover, + Select, + SelectValue, + ToggleButton, + ToggleButtonGroup, +} from 'react-aria-components'; import styles from './Toolbar.module.css'; +import { Tabs } from '@base-ui-components/react/tabs'; +import { usePlayground } from '@/utils/playground-context'; +import { usePathname } from 'next/navigation'; +import Link from 'next/link'; +import { components, layoutComponents } from '@/utils/data'; interface ToolbarProps { version: string; } -export const Toolbar = ({ version }: ToolbarProps) => { - const containerRef = useRef(null); - const { scrollY } = useScroll(); +const themes = [ + { name: 'Backstage', value: 'backstage' }, + { name: 'Spotify', value: 'spotify' }, + { name: 'Custom theme', value: 'custom' }, +]; - // Transform scroll velocity to vertical movement - const y = useTransform(scrollY, [0, 100], [0, -20], { - clamp: false, - }); +export const Toolbar = ({ version }: ToolbarProps) => { + const { + selectedTheme, + setSelectedTheme, + selectedThemeName, + setSelectedThemeName, + } = usePlayground(); + + const pathname = usePathname(); + + // Determine breadcrumb content based on current path + const getBreadcrumb = () => { + const allComponents = [...components, ...layoutComponents]; + + // Root page + if (pathname === '/') { + return { section: null, title: 'Getting Started' }; + } + + // Components index page + if (pathname === '/components') { + return { section: null, title: 'Components' }; + } + + // Component detail pages + if (pathname?.startsWith('/components/')) { + const slug = pathname.split('/components/')[1]; + const component = allComponents.find(c => c.slug === slug); + return { + section: 'Components', + sectionLink: '/components', + title: component?.title || slug, + }; + } + + // Tokens page + if (pathname === '/tokens') { + return { section: null, title: 'Tokens' }; + } + + // Changelog page + if (pathname === '/changelog') { + return { section: null, title: 'Changelog' }; + } + + return { section: null, title: '' }; + }; + + const breadcrumb = getBreadcrumb(); return ( -
-
- +
+
+ {breadcrumb.section && breadcrumb.sectionLink ? ( + <> + + {breadcrumb.section} + + + {breadcrumb.title} + + ) : ( + {breadcrumb.title} + )} +
+
+ + + Version {version} + + + + + + + + + +
- -
Version {version} - Alpha
-
- - - -
-
); }; diff --git a/docs-ui/src/components/Toolbar/theme-name.module.css b/docs-ui/src/components/Toolbar/theme-name.module.css deleted file mode 100644 index 56f3401d8d..0000000000 --- a/docs-ui/src/components/Toolbar/theme-name.module.css +++ /dev/null @@ -1,110 +0,0 @@ -.Select { - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; - height: 3rem; - padding-left: 1.25rem; - padding-right: 1rem; - margin: 0; - outline: 0; - border: 1px solid var(--color-gray-200); - border-radius: 3rem; - font-family: inherit; - font-size: 1rem; - line-height: 1.5rem; - color: var(--color-gray-900); - cursor: pointer; - user-select: none; - background-color: var(--action); - - &:focus-visible { - outline: 2px solid var(--color-blue); - outline-offset: -1px; - } -} - -.Popup { - box-sizing: border-box; - padding-block: 0.25rem; - border-radius: 0.375rem; - background-color: var(--bg); - color: var(--color-gray-900); - border: 1px solid var(--border); - padding-inline: 0.25rem; - transform-origin: var(--transform-origin); - transition: transform 150ms, opacity 150ms; - - &[data-starting-style], - &[data-ending-style] { - opacity: 0; - transform: scale(0.9); - } - - &[data-side='none'] { - transition: none; - transform: none; - opacity: 1; - } - - @media (prefers-color-scheme: light) { - outline: 1px solid var(--color-gray-200); - box-shadow: 0px 10px 15px -3px var(--color-gray-200), - 0px 4px 6px -4px var(--color-gray-200); - } - - @media (prefers-color-scheme: dark) { - outline: 1px solid var(--color-gray-300); - outline-offset: -1px; - } -} - -.Popup[data-trigger='Select'] { - min-width: var(--trigger-width); - - .ListBox { - display: block; - width: unset; - max-height: inherit; - min-height: unset; - border: none; - - .react-aria-Header { - padding-left: 1.571rem; - } - } - - .Item { - position: relative; - padding: 0 0.571rem 0 1.571rem; - height: 2rem; - display: flex; - align-items: center; - - &[data-focus-visible] { - outline: none; - } - - &[data-selected] { - font-weight: 600; - background: unset; - color: var(--text-color); - - &::before { - content: '✓'; - content: '✓' / ''; - alt: ' '; - position: absolute; - left: 4px; - } - } - - &[data-focused], - &[data-pressed] { - background: var(--bg); - color: var(--primary); - cursor: pointer; - } - } -} diff --git a/docs-ui/src/components/Toolbar/theme-name.tsx b/docs-ui/src/components/Toolbar/theme-name.tsx deleted file mode 100644 index ae9e7f9f35..0000000000 --- a/docs-ui/src/components/Toolbar/theme-name.tsx +++ /dev/null @@ -1,44 +0,0 @@ -'use client'; - -import { - Button, - ListBox, - ListBoxItem, - Popover, - Select, - SelectValue, -} from 'react-aria-components'; -import styles from './theme-name.module.css'; -import { usePlayground } from '@/utils/playground-context'; -import { RiArrowDownSLine } from '@remixicon/react'; - -const themes = [ - { name: 'Backstage', value: 'backstage' }, - { name: 'Spotify', value: 'spotify' }, - { name: 'Custom theme', value: 'custom' }, -]; - -export const ThemeNameSelector = () => { - const { selectedThemeName, setSelectedThemeName } = usePlayground(); - - return ( - - ); -}; diff --git a/docs-ui/src/components/Toolbar/theme.module.css b/docs-ui/src/components/Toolbar/theme.module.css deleted file mode 100644 index c5490a72d7..0000000000 --- a/docs-ui/src/components/Toolbar/theme.module.css +++ /dev/null @@ -1,79 +0,0 @@ -.tabs { - border-radius: 0.375rem; - width: 100%; -} - -.tabsTheme { - width: 100px; - border-radius: 0.375rem; -} - -.list { - display: flex; - position: relative; - z-index: 0; - gap: 0.25rem; -} - -.tab { - display: flex; - align-items: center; - justify-content: center; - border: 0; - margin: 0; - outline: 0; - background: none; - appearance: none; - color: var(--secondary); - user-select: none; - flex: 1; - cursor: pointer; - width: 3rem; - height: 3rem; - - &[data-selected] { - color: var(--primary); - - & p { - color: var(--primary); - } - } - - @media (hover: hover) { - &:hover { - color: var(--primary); - } - } - - &:focus-visible { - position: relative; - - &::before { - content: ''; - position: absolute; - inset: 0.25rem 0; - border-radius: 0.25rem; - outline: 2px solid var(--action); - outline-offset: -1px; - } - } -} - -.tab p { - color: var(--secondary) !important; -} - -.indicator { - position: absolute; - z-index: -1; - left: 0; - top: 50%; - translate: var(--active-tab-left) -50%; - width: var(--active-tab-width); - height: 3rem; - border-radius: 3rem; - background-color: var(--action); - transition-property: translate, width; - transition-duration: 200ms; - transition-timing-function: ease-in-out; -} diff --git a/docs-ui/src/components/Toolbar/theme.tsx b/docs-ui/src/components/Toolbar/theme.tsx deleted file mode 100644 index 7ece7bfe7b..0000000000 --- a/docs-ui/src/components/Toolbar/theme.tsx +++ /dev/null @@ -1,28 +0,0 @@ -'use client'; - -import { Tabs } from '@base-ui-components/react/tabs'; -import { usePlayground } from '@/utils/playground-context'; -import styles from './theme.module.css'; -import { RiMoonLine, RiSunLine } from '@remixicon/react'; - -export const ThemeSelector = () => { - const { selectedTheme, setSelectedTheme } = usePlayground(); - - return ( - - - - - - - - - - ); -}; diff --git a/docs-ui/src/utils/playground-context.tsx b/docs-ui/src/utils/playground-context.tsx index 2f8ffbaee4..401b079966 100644 --- a/docs-ui/src/utils/playground-context.tsx +++ b/docs-ui/src/utils/playground-context.tsx @@ -4,6 +4,7 @@ import { ReactNode, useState, useEffect, + Key, } from 'react'; import { components } from './data'; @@ -16,8 +17,8 @@ const PlaygroundContext = createContext<{ setSelectedScreenSizes: (screenSizes: string[]) => void; selectedComponents: string[]; setSelectedComponents: (components: string[]) => void; - selectedTheme: Theme; - setSelectedTheme: (theme: Theme) => void; + selectedTheme: Set; + setSelectedTheme: (keys: Set) => void; selectedThemeName: ThemeName; setSelectedThemeName: (themeName: ThemeName) => void; }>({ @@ -25,7 +26,7 @@ const PlaygroundContext = createContext<{ setSelectedScreenSizes: () => {}, selectedComponents: [], setSelectedComponents: () => {}, - selectedTheme: 'light', + selectedTheme: new Set(['light']), setSelectedTheme: () => {}, selectedThemeName: 'backstage', setSelectedThemeName: () => {}, @@ -40,16 +41,24 @@ export const PlaygroundProvider = ({ children }: { children: ReactNode }) => { const [selectedComponents, setSelectedComponents] = useState( components.map(component => component.slug), ); - const [selectedTheme, setSelectedTheme] = useState('light'); + const [selectedTheme, setSelectedTheme] = useState>( + new Set(['light']), + ); const [selectedThemeName, setSelectedThemeName] = useState('backstage'); // Load saved theme from localStorage after hydration useEffect(() => { if (isBrowser) { - const savedTheme = localStorage.getItem('theme-mode') as Theme; - if (savedTheme) { - setSelectedTheme(savedTheme); + const savedThemeString = localStorage.getItem('theme-mode'); + if (savedThemeString) { + // Parse the comma-separated string back into a Set + const themeArray = savedThemeString + .split(',') + .filter(Boolean) as Theme[]; + setSelectedTheme(new Set(themeArray)); + } else { + setSelectedTheme(new Set(['light'])); } } }, [isBrowser]); @@ -68,9 +77,9 @@ export const PlaygroundProvider = ({ children }: { children: ReactNode }) => { if (isBrowser) { document.documentElement.setAttribute( 'data-theme-mode', - selectedTheme || 'light', + Array.from(selectedTheme).join(','), ); - localStorage.setItem('theme-mode', selectedTheme || 'light'); + localStorage.setItem('theme-mode', Array.from(selectedTheme).join(',')); } }, [selectedTheme, isBrowser]);