diff --git a/.changeset/evil-phones-unite.md b/.changeset/evil-phones-unite.md new file mode 100644 index 0000000000..9a3e4ff151 --- /dev/null +++ b/.changeset/evil-phones-unite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Support `filter` parameter on the `EntityHeaderBlueprint` diff --git a/.changeset/nice-actors-cheer.md b/.changeset/nice-actors-cheer.md new file mode 100644 index 0000000000..918148ffb8 --- /dev/null +++ b/.changeset/nice-actors-cheer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Adding a more sensible default order to the default filters diff --git a/.changeset/nice-crabs-clean.md b/.changeset/nice-crabs-clean.md new file mode 100644 index 0000000000..45690e98aa --- /dev/null +++ b/.changeset/nice-crabs-clean.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Support multiple headers in new frontend system, and don't render a header until the entity has finished loading diff --git a/.changeset/twenty-pumas-brush.md b/.changeset/twenty-pumas-brush.md new file mode 100644 index 0000000000..6306fe0607 --- /dev/null +++ b/.changeset/twenty-pumas-brush.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Adds onTabSelectionChange to ui header component. diff --git a/docs-ui/package.json b/docs-ui/package.json index 8049edac8d..6fd29e0678 100644 --- a/docs-ui/package.json +++ b/docs-ui/package.json @@ -22,6 +22,7 @@ "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", "@next/mdx": "15.3.4", + "@remixicon/react": "^4.6.0", "@storybook/react": "^8.6.8", "@uiw/codemirror-themes": "^4.23.7", "@uiw/react-codemirror": "^4.23.7", @@ -32,7 +33,6 @@ "prop-types": "^15.8.1", "react": "19.1.0", "react-dom": "19.1.0", - "react-frame-component": "^5.2.7", "shiki": "^1.26.1", "storybook": "^8.6.8" }, diff --git a/docs-ui/src/app/(docs)/layout.module.css b/docs-ui/src/app/(docs)/layout.module.css deleted file mode 100644 index fa94ef3a13..0000000000 --- a/docs-ui/src/app/(docs)/layout.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.pageContainer { - width: 100%; - max-width: 960px; - margin: 0 auto; - padding: 0 24px 64px; -} diff --git a/docs-ui/src/app/(docs)/layout.tsx b/docs-ui/src/app/(docs)/layout.tsx deleted file mode 100644 index e136fd64f2..0000000000 --- a/docs-ui/src/app/(docs)/layout.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Metadata } from 'next'; - -import styles from './layout.module.css'; - -export const metadata: Metadata = { - title: 'Backstage UI', - description: 'UI library for Backstage', -}; - -export default function DocsLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return
{children}
; -} diff --git a/docs-ui/src/app/(playground)/playground/page.tsx b/docs-ui/src/app/(playground)/playground/page.tsx deleted file mode 100644 index 424cf2a707..0000000000 --- a/docs-ui/src/app/(playground)/playground/page.tsx +++ /dev/null @@ -1,87 +0,0 @@ -'use client'; - -import { ReactNode } from 'react'; -import { Grid, Flex, Text } from '../../../../../packages/ui'; -import { screenSizes } from '@/utils/data'; -import { Frame } from '@/components/Frame'; -import { usePlayground } from '@/utils/playground-context'; -import { - ButtonSnippet, - CheckboxSnippet, - HeadingSnippet, - TextSnippet, -} from '@/snippets/stories-snippets'; - -import styles from './styles.module.css'; - -export default function PlaygroundPage() { - const { selectedScreenSizes } = usePlayground(); - - const filteredScreenSizes = screenSizes.filter(item => - selectedScreenSizes.includes(item.slug), - ); - - if (filteredScreenSizes.length === 0) { - return ( -
- -
- ); - } - - return ( -
- {filteredScreenSizes.map(screenSize => ( -
- - {screenSize.title} - {screenSize.width}px - -
- - - -
-
- ))} -
- ); -} - -const Content = () => { - const { selectedComponents } = usePlayground(); - - return ( - - {selectedComponents.find(c => c === 'button') && ( - } title="Button" /> - )} - {selectedComponents.find(c => c === 'checkbox') && ( - } - title="Checkbox" - /> - )} - {selectedComponents.find(c => c === 'heading') && ( - } title="Heading" /> - )} - {selectedComponents.find(c => c === 'text') && ( - } title="Text" /> - )} - - ); -}; - -const Line = ({ content, title }: { content: ReactNode; title: string }) => { - return ( - - - {title} - - {content} - - ); -}; diff --git a/docs-ui/src/app/(playground)/playground/styles.module.css b/docs-ui/src/app/(playground)/playground/styles.module.css deleted file mode 100644 index 026e8a1bd4..0000000000 --- a/docs-ui/src/app/(playground)/playground/styles.module.css +++ /dev/null @@ -1,36 +0,0 @@ -.container { - height: 100vh; - display: flex; - flex-direction: row; - gap: 24px; - overflow-x: scroll; -} - -.containerEmpty { - padding: 78px 40px; -} - -.breakpointContainer { - flex-shrink: 0; - margin-top: 40px; - height: calc(100vh - 86px); - display: flex; - flex-direction: column; - gap: 8px; - - &:first-child { - margin-left: 48px; - } - - &:last-child { - margin-right: 48px; - } -} - -.breakpointContent { - height: 100%; - border-radius: 4px; - border: 1px solid var(--border); - background-color: var(--bg); - padding: 16px; -} diff --git a/docs-ui/src/app/(docs)/about/page.mdx b/docs-ui/src/app/about/page.mdx similarity index 100% rename from docs-ui/src/app/(docs)/about/page.mdx rename to docs-ui/src/app/about/page.mdx diff --git a/docs-ui/src/app/(docs)/changelog/page.mdx b/docs-ui/src/app/changelog/page.mdx similarity index 100% rename from docs-ui/src/app/(docs)/changelog/page.mdx rename to docs-ui/src/app/changelog/page.mdx diff --git a/docs-ui/src/app/(docs)/components/[slug]/page.tsx b/docs-ui/src/app/components/[slug]/page.tsx similarity index 100% rename from docs-ui/src/app/(docs)/components/[slug]/page.tsx rename to docs-ui/src/app/components/[slug]/page.tsx diff --git a/docs-ui/src/app/layout.module.css b/docs-ui/src/app/layout.module.css new file mode 100644 index 0000000000..7399fc2485 --- /dev/null +++ b/docs-ui/src/app/layout.module.css @@ -0,0 +1,28 @@ +.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; + background-color: var(--panel); + border-radius: 0.5rem; +} + +.content { + width: 100%; + max-width: 960px; + margin: 0 auto; +} + +@media (min-width: 768px) { + .container { + width: calc(100% - 332px - 40px); + margin-left: 332px; + margin-right: 40px; + } +} diff --git a/docs-ui/src/app/layout.tsx b/docs-ui/src/app/layout.tsx index 092bca57d4..58decbf22b 100644 --- a/docs-ui/src/app/layout.tsx +++ b/docs-ui/src/app/layout.tsx @@ -3,7 +3,7 @@ import { Sidebar } from '../components/Sidebar'; import { Toolbar } from '@/components/Toolbar'; import { Providers } from './providers'; import { CustomTheme } from '@/components/CustomTheme'; -import styles from '../css/page.module.css'; +import styles from './layout.module.css'; import '../css/globals.css'; import '/public/theme-backstage.css'; @@ -15,11 +15,31 @@ export const metadata: Metadata = { metadataBase: new URL('https://ui.backstage.io'), }; -export default function RootLayout({ +async function getLatestPackageVersion(): Promise { + try { + const response = await fetch('https://registry.npmjs.org/@backstage/ui', { + next: { revalidate: 3600 }, // Cache for 1 hour + }); + + if (!response.ok) { + throw new Error('Failed to fetch package info'); + } + + const data = await response.json(); + return data['dist-tags']?.latest; + } catch (error) { + console.error('Error fetching package version:', error); + return '0.0.0'; + } +} + +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { + const packageVersion = await getLatestPackageVersion(); + return ( -
- -
- - {children} -
- + + +
+
{children}
+ diff --git a/docs-ui/src/app/(docs)/page.mdx b/docs-ui/src/app/page.mdx similarity index 100% rename from docs-ui/src/app/(docs)/page.mdx rename to docs-ui/src/app/page.mdx diff --git a/docs-ui/src/app/(docs)/theme/iconography/page.mdx b/docs-ui/src/app/theme/iconography/page.mdx similarity index 100% rename from docs-ui/src/app/(docs)/theme/iconography/page.mdx rename to docs-ui/src/app/theme/iconography/page.mdx diff --git a/docs-ui/src/app/(docs)/theme/layout/page.mdx b/docs-ui/src/app/theme/layout/page.mdx similarity index 100% rename from docs-ui/src/app/(docs)/theme/layout/page.mdx rename to docs-ui/src/app/theme/layout/page.mdx diff --git a/docs-ui/src/app/(docs)/theme/responsive/page.mdx b/docs-ui/src/app/theme/responsive/page.mdx similarity index 100% rename from docs-ui/src/app/(docs)/theme/responsive/page.mdx rename to docs-ui/src/app/theme/responsive/page.mdx diff --git a/docs-ui/src/app/(docs)/theme/theming/page.mdx b/docs-ui/src/app/theme/theming/page.mdx similarity index 100% rename from docs-ui/src/app/(docs)/theme/theming/page.mdx rename to docs-ui/src/app/theme/theming/page.mdx diff --git a/docs-ui/src/app/(docs)/theme/typography/page.mdx b/docs-ui/src/app/theme/typography/page.mdx similarity index 100% rename from docs-ui/src/app/(docs)/theme/typography/page.mdx rename to docs-ui/src/app/theme/typography/page.mdx diff --git a/docs-ui/src/components/ChangelogComponent/index.tsx b/docs-ui/src/components/ChangelogComponent/index.tsx new file mode 100644 index 0000000000..f8832b0731 --- /dev/null +++ b/docs-ui/src/components/ChangelogComponent/index.tsx @@ -0,0 +1,32 @@ +import { changelog } from '@/utils/changelog'; +import { MDXRemote } from 'next-mdx-remote-client/rsc'; +import { formattedMDXComponents } from '@/mdx-components'; +import type { Component } from '@/utils/changelog'; + +export const ChangelogComponent = ({ component }: { component: Component }) => { + const componentChangelog = changelog.filter(c => + c.components.includes(component), + ); + + return ( + { + const prs = + change.prs.length > 0 && + change.prs + .map( + pr => + `[#${pr}](https://github.com/backstage/backstage/pull/${pr})`, + ) + .join(', '); + return `- \`${change.version}\` - ${change.description} ${prs}`; + }) + .join('\n')}`} + /> + ); +}; diff --git a/docs-ui/src/components/ComponentInfos/ComponentInfos.tsx b/docs-ui/src/components/ComponentInfos/ComponentInfos.tsx deleted file mode 100644 index a29b01cba0..0000000000 --- a/docs-ui/src/components/ComponentInfos/ComponentInfos.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import type { ComponentInfosProps } from './types'; -import styles from '@/css/mdx.module.css'; -import Link from 'next/link'; -import { changelog } from '@/utils/changelog'; -import { MDXRemote } from 'next-mdx-remote-client/rsc'; -import { formattedMDXComponents } from '@/mdx-components'; - -export const ComponentInfos = ({ - usageCode, - component, - classNames, -}: ComponentInfosProps) => { - const componentChangelog = changelog.filter(c => - c.components.includes(component), - ); - - return ( - - - Usage - {classNames && classNames.length > 0 && Theming} - Changelog - - - - - {classNames && classNames.length > 0 && ( - -

- We recommend starting with our{' '} - - global tokens - {' '} - to customize the library and align it with your brand. For - additional flexibility, you can use the provided class names for - each element listed below. -

- `- \`${className}\``) - .join('\n')}`} - /> -
- )} - - { - const prs = - change.prs.length > 0 && - change.prs - .map( - pr => - `[#${pr}](https://github.com/backstage/backstage/pull/${pr})`, - ) - .join(', '); - return `- \`${change.version}\` - ${change.description} ${prs}`; - }) - .join('\n')}`} - /> - -
- ); -}; diff --git a/docs-ui/src/components/ComponentInfos/index.ts b/docs-ui/src/components/ComponentInfos/index.ts deleted file mode 100644 index 04dbaeb1c6..0000000000 --- a/docs-ui/src/components/ComponentInfos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ComponentInfos } from './ComponentInfos'; diff --git a/docs-ui/src/components/ComponentInfos/types.ts b/docs-ui/src/components/ComponentInfos/types.ts deleted file mode 100644 index 8814ff479a..0000000000 --- a/docs-ui/src/components/ComponentInfos/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Component } from '@/utils/changelog'; -import { ReactNode } from 'react'; - -export interface ComponentInfosProps { - usageCode?: string; - classNames?: string[]; - component: Component; - children: ReactNode; -} diff --git a/docs-ui/src/components/CustomTheme/styles.module.css b/docs-ui/src/components/CustomTheme/styles.module.css index 3f0a5eb14e..05604c48be 100644 --- a/docs-ui/src/components/CustomTheme/styles.module.css +++ b/docs-ui/src/components/CustomTheme/styles.module.css @@ -1,5 +1,6 @@ .container { position: fixed; + z-index: 100; bottom: 16px; right: 16px; width: 240px; diff --git a/docs-ui/src/components/Frame/index.tsx b/docs-ui/src/components/Frame/index.tsx deleted file mode 100644 index d6255401dd..0000000000 --- a/docs-ui/src/components/Frame/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -'use client'; - -import { useEffect, useState } from 'react'; -import ReactFrame from 'react-frame-component'; - -export const Frame = ({ children }: { children: React.ReactNode }) => { - const [show, setShow] = useState(false); - - useEffect(() => { - setShow(true); - }, []); - - if (!show) return null; - - return ( -
`} - mountTarget=".frame-root" - head={ - <> - - - - - } - > - {children} -
- ); -}; diff --git a/docs-ui/src/components/PageTitle/PageTitle.module.css b/docs-ui/src/components/PageTitle/PageTitle.module.css new file mode 100644 index 0000000000..f03b53db96 --- /dev/null +++ b/docs-ui/src/components/PageTitle/PageTitle.module.css @@ -0,0 +1,12 @@ +.container { + margin-bottom: 40px; +} + +.component { + font-size: 11px; + text-transform: uppercase; + font-weight: 500; + letter-spacing: 0.05em; + color: var(--secondary); + margin-bottom: 6px; +} diff --git a/docs-ui/src/components/PageTitle/index.tsx b/docs-ui/src/components/PageTitle/index.tsx new file mode 100644 index 0000000000..540e19eb36 --- /dev/null +++ b/docs-ui/src/components/PageTitle/index.tsx @@ -0,0 +1,23 @@ +import { MDXRemote } from 'next-mdx-remote-client/rsc'; +import { formattedMDXComponents } from '@/mdx-components'; +import styles from './PageTitle.module.css'; + +export const PageTitle = ({ + title, + description, + type = 'component', +}: { + title: string; + type?: string; + description: string; +}) => { + return ( +
+
{type}
+ +
+ ); +}; diff --git a/docs-ui/src/components/Sidebar/Sidebar.module.css b/docs-ui/src/components/Sidebar/Sidebar.module.css index 7d2a13a3f3..2b88945c47 100644 --- a/docs-ui/src/components/Sidebar/Sidebar.module.css +++ b/docs-ui/src/components/Sidebar/Sidebar.module.css @@ -6,99 +6,46 @@ .sidebar { display: block; position: fixed; - top: 16px; - left: 16px; - border-radius: 8px; - width: 300px; - height: calc(100vh - 32px); - box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.025); + top: 112px; + left: 0; + width: 332px; + height: calc(100vh - 112px); color: var(--primary); - background-color: var(--panel); - border: 1px solid var(--border); overflow: hidden; } } +.version { + font-size: 0.875rem; + font-weight: 500; + color: var(--secondary); + margin-inline: 32px; + border-bottom: 1px solid var(--border); + padding-bottom: 12px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.versionLinks { + display: flex; + align-items: center; + gap: 8px; + + a { + color: var(--secondary); + transition: color 0.2s ease-in-out; + + &:hover { + color: var(--primary); + } + } +} + .root { height: 100%; } -.viewport { - overflow: scroll; - height: 100%; -} - -.content { - padding: 0 20px 20px; -} - -.logoContainer { - padding-left: 6px; - padding-top: 20px; -} - -.logo path { - fill: var(--primary); -} - -.menu { - display: flex; - flex-direction: row; - position: relative; -} - -.section { - width: 100%; - display: flex; - flex-direction: column; - gap: 2px; -} - -.sectionTitle { - font-size: 0.875rem; - font-weight: 600; - padding: 12px 0; - color: var(--primary); - margin-top: 24px; -} - -.line { - text-decoration: none; - align-items: center; - width: 100%; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - height: 26px; - padding: 0 12px; - border-radius: 4px; - - &:hover { - background-color: var(--surface-1); - transition: background-color 0.2s ease-in-out; - } -} - -.line.active { - background-color: var(--surface-1); -} - -.line.active .lineTitle { - color: var(--primary); -} - -.lineTitle { - font-size: 14px; - font-weight: 400; - color: var(--secondary); -} - -.lineStatus { - font-size: 14px; - color: var(--secondary); -} - .scrollbar { display: flex; justify-content: center; @@ -110,7 +57,6 @@ transition: opacity 150ms 300ms; right: -20px; - &[data-hovering], &[data-scrolling] { opacity: 1; transition-duration: 75ms; @@ -130,3 +76,70 @@ border-radius: inherit; background-color: rgba(0, 0, 0, 0.2); } + +.viewport { + overflow: scroll; + height: 100%; +} + +.content { + padding: 0 20px 64px; +} + +.menu { + display: flex; + flex-direction: row; + position: relative; +} + +.section { + width: 100%; + display: flex; + flex-direction: column; + gap: 2px; +} + +.sectionTitle { + font-size: 0.6875rem; + font-weight: 500; + padding: 0 12px 4px; + color: var(--secondary); + margin-top: 40px; + text-transform: uppercase; +} + +.line { + text-decoration: none; + align-items: center; + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + height: 26px; + padding: 0 12px; + border-radius: 4px; + + &:hover { + background-color: var(--action); + } +} + +.line.active { + background-color: var(--action); +} + +.line.active .lineTitle { + color: var(--primary); +} + +.lineTitle { + font-size: 14px; + font-weight: 400; + color: var(--primary); +} + +.lineStatus { + font-size: 14px; + color: var(--secondary); +} diff --git a/docs-ui/src/components/Sidebar/docs.tsx b/docs-ui/src/components/Sidebar/docs.tsx deleted file mode 100644 index da501b151a..0000000000 --- a/docs-ui/src/components/Sidebar/docs.tsx +++ /dev/null @@ -1,82 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import { components, overview, layoutComponents, theme } from '@/utils/data'; -import { motion } from 'motion/react'; -import styles from './Sidebar.module.css'; -import { usePathname } from 'next/navigation'; -import { Fragment } from 'react'; - -const data = [ - { - title: 'Overview', - content: overview, - url: '', - }, - { - title: 'Theme', - content: theme, - url: '/theme', - }, - { - title: 'Layout Components', - content: layoutComponents, - url: '/components', - }, - { - title: 'Components', - content: components, - url: '/components', - }, -]; - -export const Docs = () => { - const pathname = usePathname(); - const isPlayground = pathname.includes('/playground'); - - return ( - - {data.map(section => { - return ( - -
{section.title}
- - {section.content.map(item => { - const isActive = pathname === `${section.url}/${item.slug}`; - - return ( - -
{item.title}
-
- {item.status === 'alpha' && 'Alpha'} - {item.status === 'beta' && 'Beta'} - {item.status === 'inProgress' && 'In Progress'} - {item.status === 'stable' && 'Stable'} - {item.status === 'deprecated' && 'Deprecated'} -
- - ); - })} -
- ); - })} -
- ); -}; diff --git a/docs-ui/src/components/Sidebar/index.tsx b/docs-ui/src/components/Sidebar/index.tsx index b3fdbfe168..4d86398ef7 100644 --- a/docs-ui/src/components/Sidebar/index.tsx +++ b/docs-ui/src/components/Sidebar/index.tsx @@ -1,57 +1,111 @@ -import styles from './Sidebar.module.css'; -import { Docs } from './docs'; -import { Playground } from './playground'; -import Link from 'next/link'; -import { ScrollArea } from '@base-ui-components/react/scroll-area'; +'use client'; + +import styles from './Sidebar.module.css'; +import { components, overview, layoutComponents, theme } from '@/utils/data'; +import { ScrollArea } from '@base-ui-components/react/scroll-area'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { motion } from 'motion/react'; +import { Fragment } from 'react'; +import { RiGithubLine, RiNpmjsLine } from '@remixicon/react'; + +const data = [ + { + title: 'Overview', + content: overview, + url: '', + }, + { + title: 'Theme', + content: theme, + url: '/theme', + }, + { + title: 'Layout Components', + content: layoutComponents, + url: '/components', + }, + { + title: 'Components', + content: components, + url: '/components', + }, +]; + +interface SidebarProps { + version: string; +} + +export const Sidebar = ({ version }: SidebarProps) => { + const pathname = usePathname(); + const isPlayground = pathname.includes('/playground'); -export const Sidebar = () => { return (
+
+ Version {version} + +
-
- - - - - - - - - - - - - - - - - - -
- - + + {data.map(section => { + return ( + +
{section.title}
+ + {section.content.map(item => { + const isActive = + pathname === `${section.url}/${item.slug}`; + + return ( + +
{item.title}
+
+ {item.status === 'alpha' && 'Alpha'} + {item.status === 'beta' && 'Beta'} + {item.status === 'inProgress' && 'In Progress'} + {item.status === 'stable' && 'Stable'} + {item.status === 'deprecated' && 'Deprecated'} +
+ + ); + })} +
+ ); + })} +
diff --git a/docs-ui/src/components/Sidebar/playground.tsx b/docs-ui/src/components/Sidebar/playground.tsx deleted file mode 100644 index d5dd9db892..0000000000 --- a/docs-ui/src/components/Sidebar/playground.tsx +++ /dev/null @@ -1,77 +0,0 @@ -'use client'; - -import { components } from '@/utils/data'; -import { Checkbox } from '@backstage/ui'; -import { motion } from 'motion/react'; -import styles from './Sidebar.module.css'; -import { usePathname } from 'next/navigation'; -import { screenSizes } from '@/utils/data'; -import { usePlayground } from '@/utils/playground-context'; - -export const Playground = () => { - const pathname = usePathname(); - const isPlayground = pathname.includes('/playground'); - const { - selectedScreenSizes, - setSelectedScreenSizes, - selectedComponents, - setSelectedComponents, - } = usePlayground(); - - const handleComponentCheckboxChange = (slug: string) => { - if (selectedComponents.find(item => item === slug)) { - const res = selectedComponents.filter(item => item !== slug); - setSelectedComponents(res); - } else { - setSelectedComponents([...selectedComponents, slug]); - } - }; - - const handleCheckboxChange = (slug: string) => { - if (selectedScreenSizes.find(item => item === slug)) { - const res = selectedScreenSizes.filter(item => item !== slug); - setSelectedScreenSizes(res); - } else { - setSelectedScreenSizes([...selectedScreenSizes, slug]); - } - }; - - return ( - -
Components
- {components.map(({ slug, title }) => ( -
-
{title}
- handleComponentCheckboxChange(slug)} - /> -
- ))} -
Screen sizes
- {screenSizes.map(({ slug, title }) => ( -
-
{title}
- handleCheckboxChange(slug)} - /> -
- ))} -
- ); -}; diff --git a/docs-ui/src/components/Theming/index.tsx b/docs-ui/src/components/Theming/index.tsx new file mode 100644 index 0000000000..fb669253a2 --- /dev/null +++ b/docs-ui/src/components/Theming/index.tsx @@ -0,0 +1,52 @@ +import { MDXRemote } from 'next-mdx-remote-client/rsc'; +import { formattedMDXComponents } from '@/mdx-components'; +import { Component } from '@/utils/changelog'; +import { componentDefinitions } from '../../../../packages/ui/src/utils/componentDefinitions'; +import type { DataAttributeValues } from '../../../../packages/ui/src/types'; + +export function Theming({ component }: { component: Component }) { + const componentDefinition = componentDefinitions[component]; + + if (!componentDefinition) { + return null; + } + + const classNames = componentDefinition.classNames; + const dataAttributes = componentDefinition.dataAttributes; + + // Get the first class name + const firstClassName = Object.values(classNames)[0]; + + // Create array of selectors combining first class name with data attributes + const selectorArray: string[] = []; + + if (dataAttributes) { + Object.entries(dataAttributes).forEach( + ([attributeName, attributeValues]) => { + (attributeValues as DataAttributeValues).forEach(value => { + selectorArray.push( + `${firstClassName}[data-${attributeName}="${value}"]`, + ); + }); + }, + ); + } + + const classNamesArray = [ + `${firstClassName}`, + ...selectorArray, + ...Object.values(classNames).slice(1), + ]; + + return ( + `- \`${selector}\``).join('\n')} + `} + /> + ); +} diff --git a/docs-ui/src/components/Toolbar/Toolbar.module.css b/docs-ui/src/components/Toolbar/Toolbar.module.css new file mode 100644 index 0000000000..8142c5464e --- /dev/null +++ b/docs-ui/src/components/Toolbar/Toolbar.module.css @@ -0,0 +1,44 @@ +.toolbar { + position: fixed; + display: flex; + top: 0; + left: 32px; + right: 32px; + height: 112px; + z-index: 10; + align-items: center; + justify-content: space-between; + padding-left: 0.25rem; +} + +@media (min-width: 960px) { + .toolbar { + left: 332px; + right: 40px; + } +} + +.left { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.name { + margin-top: 3px; + font-size: 28px; + color: #26c9ad; + font-weight: 500; +} + +.actions { + display: none; +} + +@media (min-width: 600px) { + .actions { + display: flex; + align-items: center; + gap: 1rem; + } +} diff --git a/docs-ui/src/components/Toolbar/Toolbar.tsx b/docs-ui/src/components/Toolbar/Toolbar.tsx new file mode 100644 index 0000000000..952b48f5ac --- /dev/null +++ b/docs-ui/src/components/Toolbar/Toolbar.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { ThemeSelector } from './theme'; +import { ThemeNameSelector } from './theme-name'; +import styles from './Toolbar.module.css'; + +export const Toolbar = () => { + return ( +
+
+ + + +
UI
+
+
+ + +
+
+ ); +}; diff --git a/docs-ui/src/components/Toolbar/index.ts b/docs-ui/src/components/Toolbar/index.ts new file mode 100644 index 0000000000..f362defa5e --- /dev/null +++ b/docs-ui/src/components/Toolbar/index.ts @@ -0,0 +1 @@ +export { Toolbar } from './Toolbar'; diff --git a/docs-ui/src/components/Toolbar/index.tsx b/docs-ui/src/components/Toolbar/index.tsx deleted file mode 100644 index eac51f1857..0000000000 --- a/docs-ui/src/components/Toolbar/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { ThemeSelector } from './theme'; -import { ThemeNameSelector } from './theme-name'; -import styles from './styles.module.css'; -import { Nav } from './nav'; - -export const Toolbar = () => { - return ( -
-
-
-
- - -
-
- ); -}; diff --git a/docs-ui/src/components/Toolbar/nav.module.css b/docs-ui/src/components/Toolbar/nav.module.css deleted file mode 100644 index 56501f186e..0000000000 --- a/docs-ui/src/components/Toolbar/nav.module.css +++ /dev/null @@ -1,78 +0,0 @@ -.tabs { - display: none; -} - -@media (min-width: 768px) { - .tabs { - display: block; - height: 60px; - } -} - -.tabsTheme { - width: 142px; - border-radius: 0.375rem; - background-color: var(--bg); - transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; -} - -.list { - display: flex; - position: relative; - z-index: 0; - gap: 2rem; -} - -.tab { - all: unset; - height: 60px; - color: var(--secondary); - font-size: 0.875rem; - font-weight: 600; - cursor: pointer; - - &:hover { - transition: color 0.2s ease-in-out; - color: var(--primary); - } - - &[data-selected] { - color: var(--primary); - - & p { - color: var(--primary); - } - } - - &:focus-visible { - position: relative; - - &::before { - content: ''; - position: absolute; - inset: 0.25rem 0; - border-radius: 0.25rem; - outline: 2px solid var(--panel); - outline-offset: -1px; - } - } -} - -.tab p { - color: var(--secondary) !important; -} - -.indicator { - position: absolute; - z-index: -1; - left: 0; - bottom: -1px; - translate: var(--active-tab-left) -50%; - width: var(--active-tab-width); - height: 1px; - border-radius: 0.25rem; - background-color: var(--primary); - transition-property: translate, width; - transition-duration: 200ms; - transition-timing-function: ease-in-out; -} diff --git a/docs-ui/src/components/Toolbar/nav.tsx b/docs-ui/src/components/Toolbar/nav.tsx deleted file mode 100644 index 017599581e..0000000000 --- a/docs-ui/src/components/Toolbar/nav.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client'; - -import { Tabs } from '@base-ui-components/react/tabs'; -import { usePathname } from 'next/navigation'; -import { useRouter } from 'next/navigation'; -import styles from './nav.module.css'; - -export const Nav = () => { - const pathname = usePathname(); - const router = useRouter(); - - const onValueChange = (value: string) => { - if (value === 'docs') { - router.push('/'); - } else { - router.push('/playground'); - } - }; - - return ( - - - { - router.push('/'); - }} - > - Documentation - - { - router.push('/playground'); - }} - > - Playground - - - - - ); -}; diff --git a/docs-ui/src/components/Toolbar/styles.module.css b/docs-ui/src/components/Toolbar/styles.module.css deleted file mode 100644 index 5700e876ab..0000000000 --- a/docs-ui/src/components/Toolbar/styles.module.css +++ /dev/null @@ -1,22 +0,0 @@ -.toolbar { - position: sticky; - top: 16px; - left: 0; - right: 0; - border-radius: 0.5rem; - z-index: 10; - background-color: var(--panel); - height: 60px; - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 1.5rem; - box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.025); - border: 1px solid var(--border); -} - -.actions { - display: flex; - align-items: center; - gap: 1rem; -} diff --git a/docs-ui/src/components/Toolbar/theme-name.module.css b/docs-ui/src/components/Toolbar/theme-name.module.css index 02644e8ea0..58471b8fd7 100644 --- a/docs-ui/src/components/Toolbar/theme-name.module.css +++ b/docs-ui/src/components/Toolbar/theme-name.module.css @@ -4,20 +4,20 @@ align-items: center; justify-content: space-between; gap: 0.75rem; - height: 2rem; - padding-left: 0.875rem; - padding-right: 0.75rem; + height: 3rem; + padding-left: 1.25rem; + padding-right: 1rem; margin: 0; outline: 0; border: 1px solid var(--color-gray-200); - border-radius: 0.375rem; + 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(--surface-1); + background-color: var(--action); &:focus-visible { outline: 2px solid var(--color-blue); diff --git a/docs-ui/src/components/Toolbar/theme.module.css b/docs-ui/src/components/Toolbar/theme.module.css index a7895ad840..c5490a72d7 100644 --- a/docs-ui/src/components/Toolbar/theme.module.css +++ b/docs-ui/src/components/Toolbar/theme.module.css @@ -1,20 +1,17 @@ .tabs { border-radius: 0.375rem; width: 100%; - background-color: var(--surface-1); } .tabsTheme { width: 100px; border-radius: 0.375rem; - background-color: var(--surface-1); } .list { display: flex; position: relative; z-index: 0; - padding-inline: 0.25rem; gap: 0.25rem; } @@ -29,9 +26,10 @@ appearance: none; color: var(--secondary); user-select: none; - height: 2rem; flex: 1; cursor: pointer; + width: 3rem; + height: 3rem; &[data-selected] { color: var(--primary); @@ -55,7 +53,7 @@ position: absolute; inset: 0.25rem 0; border-radius: 0.25rem; - outline: 2px solid var(--panel); + outline: 2px solid var(--action); outline-offset: -1px; } } @@ -72,9 +70,9 @@ top: 50%; translate: var(--active-tab-left) -50%; width: var(--active-tab-width); - height: 1.5rem; - border-radius: 0.25rem; - background-color: var(--panel); + 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/content/components/avatar.mdx b/docs-ui/src/content/components/avatar.mdx index f5d39809ab..be1fb070d1 100644 --- a/docs-ui/src/content/components/avatar.mdx +++ b/docs-ui/src/content/components/avatar.mdx @@ -8,11 +8,14 @@ import { snippetSizes, snippetFallback, } from './avatar.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Avatar - -An avatar component with a fallback for initials. + `} /> - +## Usage + + ## API reference @@ -63,3 +57,7 @@ If the image is not available, the avatar will show the initials of the name. preview={} code={snippetFallback} /> + + + + diff --git a/docs-ui/src/content/components/box.mdx b/docs-ui/src/content/components/box.mdx index 01d8e2ecec..84fca48349 100644 --- a/docs-ui/src/content/components/box.mdx +++ b/docs-ui/src/content/components/box.mdx @@ -10,11 +10,15 @@ import { boxResponsiveSnippet, } from './box.props'; import { spacingPropDefs } from '@/utils/propDefs'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Box - -Box is the lowest-level component in Backstage UI. It provides a consistent API for styling and layout. + - +## Usage + + ## API reference @@ -53,3 +59,7 @@ A simple example of how to use the Box component. Here's a view when buttons are responsive. + + + + diff --git a/docs-ui/src/content/components/button-icon.mdx b/docs-ui/src/content/components/button-icon.mdx index 6d65f590df..e1f27e0601 100644 --- a/docs-ui/src/content/components/button-icon.mdx +++ b/docs-ui/src/content/components/button-icon.mdx @@ -12,11 +12,14 @@ import { buttonIconResponsiveSnippet, buttonIconAsLinkSnippet, } from './button-icon.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# ButtonIcon - -A button component with a single icon that can be used to trigger actions. + - +## Usage + + ## API reference @@ -78,3 +79,7 @@ Here's a view when buttons are disabled. Here's a view when buttons are responsive. + + + + diff --git a/docs-ui/src/content/components/button-link.mdx b/docs-ui/src/content/components/button-link.mdx index e4912e918c..5ce538838d 100644 --- a/docs-ui/src/content/components/button-link.mdx +++ b/docs-ui/src/content/components/button-link.mdx @@ -11,11 +11,14 @@ import { buttonLinkDisabledSnippet, buttonLinkResponsiveSnippet, } from './button-link.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# ButtonLink - -A button component that can be used as a link. + - +## Usage + + ## API reference @@ -89,3 +90,7 @@ Here's a view when buttons are disabled. Here's a view when buttons are responsive. + + + + diff --git a/docs-ui/src/content/components/button.mdx b/docs-ui/src/content/components/button.mdx index 0ea0c5e33e..a25115012b 100644 --- a/docs-ui/src/content/components/button.mdx +++ b/docs-ui/src/content/components/button.mdx @@ -12,11 +12,14 @@ import { buttonResponsiveSnippet, buttonAsLinkSnippet, } from './button.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; -# Button - -A button component that can be used to trigger actions. + - +## Usage + + ## API reference @@ -102,3 +103,7 @@ If you want to use a button as a link, please use the `ButtonLink` component. preview={} code={buttonAsLinkSnippet} /> + + + + diff --git a/docs-ui/src/content/components/card.mdx b/docs-ui/src/content/components/card.mdx index f08e68f88b..06c3a5bc2e 100644 --- a/docs-ui/src/content/components/card.mdx +++ b/docs-ui/src/content/components/card.mdx @@ -12,11 +12,14 @@ import { cardLongBodySnippet, cardListRowSnippet, } from './card.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Card - -A card component that can be used to display content in a box. + - +## Usage + + ## API reference @@ -82,3 +83,7 @@ Here's a view when card has a list. code={cardListRowSnippet} open /> + + + + diff --git a/docs-ui/src/content/components/checkbox.mdx b/docs-ui/src/content/components/checkbox.mdx index 2daef1428e..5770396ebb 100644 --- a/docs-ui/src/content/components/checkbox.mdx +++ b/docs-ui/src/content/components/checkbox.mdx @@ -8,11 +8,14 @@ import { checkboxDefaultSnippet, checkboxVariantsSnippet, } from './checkbox.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Checkbox - -A checkbox component that can be used to trigger actions. + - +## Usage + + ## API reference @@ -44,3 +45,7 @@ Here's a view when checkboxes have different variants. preview={} code={checkboxVariantsSnippet} /> + + + + diff --git a/docs-ui/src/content/components/collapsible.mdx b/docs-ui/src/content/components/collapsible.mdx index 600934424a..948da323fe 100644 --- a/docs-ui/src/content/components/collapsible.mdx +++ b/docs-ui/src/content/components/collapsible.mdx @@ -11,11 +11,14 @@ import { collapsibleTriggerSnippet, collapsibleOpenSnippet, } from './collapsible.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Collapsible - -An avatar component with a fallback for initials. + - +## Usage + + ## API reference @@ -69,3 +66,7 @@ Open the panel by default by setting the `defaultOpen` prop to `true`. preview={} code={collapsibleOpenSnippet} /> + + + + diff --git a/docs-ui/src/content/components/container.mdx b/docs-ui/src/content/components/container.mdx index cf47c76a21..05615cb1c2 100644 --- a/docs-ui/src/content/components/container.mdx +++ b/docs-ui/src/content/components/container.mdx @@ -9,12 +9,15 @@ import { containerSimpleSnippet, containerResponsiveSnippet, } from './container.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Container - -The container component let you use our default max-width and center the -content on the page. + - +## Usage + + ## API reference @@ -46,3 +47,7 @@ The Container component also supports responsive values, making it easy to create responsive designs. + + + + diff --git a/docs-ui/src/content/components/flex.mdx b/docs-ui/src/content/components/flex.mdx index 95b0e1be41..89cd2e380a 100644 --- a/docs-ui/src/content/components/flex.mdx +++ b/docs-ui/src/content/components/flex.mdx @@ -12,11 +12,15 @@ import { flexAlignSnippet, } from './flex.props'; import { spacingPropDefs } from '@/utils/propDefs'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Flex - -A responsive flex container component for vertical stacking with customizable gaps. + - +## Usage + + ## API reference @@ -60,3 +62,7 @@ The Flex component also supports responsive alignment, making it easy to create responsive designs. + + + + diff --git a/docs-ui/src/content/components/grid.mdx b/docs-ui/src/content/components/grid.mdx index 1fc5006f39..0ffdba6a66 100644 --- a/docs-ui/src/content/components/grid.mdx +++ b/docs-ui/src/content/components/grid.mdx @@ -14,12 +14,15 @@ import { gridResponsiveSnippet, gridStartEndSnippet, } from './grid.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Grid - -A layout component that helps to create simple column-based layouts as well as -more complex ones. + - +## Usage + + ## API reference @@ -91,3 +92,7 @@ responsive designs. The start and end props can be used to position the item in the grid. + + + + diff --git a/docs-ui/src/content/components/icon.mdx b/docs-ui/src/content/components/icon.mdx index 16e190b618..91860a7e5e 100644 --- a/docs-ui/src/content/components/icon.mdx +++ b/docs-ui/src/content/components/icon.mdx @@ -7,11 +7,14 @@ import { iconUsageSnippet, iconDefaultSnippet, } from './icon.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Icon - -Icons are used to represent an action or a state. + - +## Usage + + ## API reference + + + + diff --git a/docs-ui/src/content/components/link.mdx b/docs-ui/src/content/components/link.mdx index 6618848680..e0c1be353f 100644 --- a/docs-ui/src/content/components/link.mdx +++ b/docs-ui/src/content/components/link.mdx @@ -16,11 +16,14 @@ import { linkRouterSnippet, linkTruncateSnippet, } from './link.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Link - -A link component that supports both internal routing and external navigation. + - +## Usage + + ## API reference @@ -113,3 +96,7 @@ The `Link` component has a `truncate` prop that can be used to truncate the text preview={} code={linkTruncateSnippet} /> + + + + diff --git a/docs-ui/src/content/components/menu.mdx b/docs-ui/src/content/components/menu.mdx index 8bff6b2f47..c1b67b5034 100644 --- a/docs-ui/src/content/components/menu.mdx +++ b/docs-ui/src/content/components/menu.mdx @@ -13,11 +13,14 @@ import { menuHoverSnippet, menuSubmenuSnippet, } from './menu.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Menu - -A list of actions in a dropdown, enhanced with keyboard navigation. + - +## Usage + + ## API reference @@ -103,3 +88,7 @@ You can nest menus to create a more complex navigation structure. preview={} code={menuSubmenuSnippet} /> + + + + diff --git a/docs-ui/src/content/components/radio-group.mdx b/docs-ui/src/content/components/radio-group.mdx index dc6d3a8bb1..ab3d28ad81 100644 --- a/docs-ui/src/content/components/radio-group.mdx +++ b/docs-ui/src/content/components/radio-group.mdx @@ -12,11 +12,15 @@ import { radioGroupValidationSnippet, radioGroupReadOnlySnippet, } from './radio-group.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; -# RadioGroup - -A radio group allows a user to select a single item from a list of mutually exclusive options. + - +## Usage + + ## API reference @@ -96,3 +98,7 @@ You can make the radio group read only by adding the `isReadOnly` prop to the `R preview={} code={radioGroupReadOnlySnippet} /> + + + + diff --git a/docs-ui/src/content/components/search-field.mdx b/docs-ui/src/content/components/search-field.mdx index 2f855554b0..cc8ae98685 100644 --- a/docs-ui/src/content/components/search-field.mdx +++ b/docs-ui/src/content/components/search-field.mdx @@ -8,11 +8,15 @@ import { searchFieldSizesSnippet, searchFieldDescriptionSnippet, } from './search-field.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; -# SearchField - -A SearchField is a text field designed for searches. + - +## Usage + + ## API reference @@ -67,3 +58,7 @@ Here's a simple SearchField with a description. preview={} code={searchFieldDescriptionSnippet} /> + + + + diff --git a/docs-ui/src/content/components/select.mdx b/docs-ui/src/content/components/select.mdx index c5fc5d1dd4..09a852e871 100644 --- a/docs-ui/src/content/components/select.mdx +++ b/docs-ui/src/content/components/select.mdx @@ -12,11 +12,14 @@ import { selectResponsiveSnippet, selectIconSnippet, } from './select.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Select - -A common form component for choosing a predefined value in a dropdown menu. + - +## Usage + + ## API reference @@ -100,3 +92,7 @@ Here's a view when the select is disabled. Here's a view when the select is responsive. + + + + diff --git a/docs-ui/src/content/components/skeleton.mdx b/docs-ui/src/content/components/skeleton.mdx index 3f368da2dc..f0d3fbc39a 100644 --- a/docs-ui/src/content/components/skeleton.mdx +++ b/docs-ui/src/content/components/skeleton.mdx @@ -9,11 +9,14 @@ import { skeletonDemo1Snippet, skeletonDemo2Snippet, } from './skeleton.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Skeleton - -Use to show a placeholder while content is loading. + - +## Usage + + ## API reference @@ -57,3 +58,7 @@ You can use a mix of different sizes to create a more complex skeleton. code={skeletonDemo2Snippet} open /> + + + + diff --git a/docs-ui/src/content/components/switch.mdx b/docs-ui/src/content/components/switch.mdx index 2d814b69b3..acf588e37d 100644 --- a/docs-ui/src/content/components/switch.mdx +++ b/docs-ui/src/content/components/switch.mdx @@ -3,11 +3,14 @@ import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { SwitchSnippet } from '@/snippets/stories-snippets'; import { switchPropDefs, snippetUsage } from './switch.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Switch - -A control that indicates whether a setting is on or off. + `} /> - +## Usage + + ## API reference @@ -39,3 +40,7 @@ A switch can be disabled using the `isDisabled` prop. preview={} code={``} /> + + + + diff --git a/docs-ui/src/content/components/table.mdx b/docs-ui/src/content/components/table.mdx index f169bcd7f9..03b362c8c8 100644 --- a/docs-ui/src/content/components/table.mdx +++ b/docs-ui/src/content/components/table.mdx @@ -1 +1,6 @@ -# Table (Coming soon) +import { PageTitle } from '@/components/PageTitle'; + + diff --git a/docs-ui/src/content/components/tabs.mdx b/docs-ui/src/content/components/tabs.mdx index d3188b8d96..dd0552b436 100644 --- a/docs-ui/src/content/components/tabs.mdx +++ b/docs-ui/src/content/components/tabs.mdx @@ -10,11 +10,14 @@ import { tabsUsageSnippet, tabsDefaultSnippet, } from './tabs.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Tabs - -A component for toggling between related panels on the same page. + - +## Usage + + ## API reference @@ -59,3 +54,7 @@ An individual interactive tab button that toggles the corresponding panel. Rende A panel displayed when the corresponding tab is active. Renders a `
` element. + + + + diff --git a/docs-ui/src/content/components/text-field.mdx b/docs-ui/src/content/components/text-field.mdx index 03307d2e36..ad90783ffb 100644 --- a/docs-ui/src/content/components/text-field.mdx +++ b/docs-ui/src/content/components/text-field.mdx @@ -8,11 +8,15 @@ import { textFieldSizesSnippet, textFieldDescriptionSnippet, } from './text-field.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; -# TextField - -A text field component for your forms. + - +## Usage + + ## API reference @@ -66,3 +58,7 @@ Here's a simple TextField with a description. preview={} code={textFieldDescriptionSnippet} /> + + + + diff --git a/docs-ui/src/content/components/text.mdx b/docs-ui/src/content/components/text.mdx index 921a942dae..48b461aa8c 100644 --- a/docs-ui/src/content/components/text.mdx +++ b/docs-ui/src/content/components/text.mdx @@ -12,11 +12,14 @@ import { textResponsiveSnippet, textColorsSnippet, } from './text.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Text - -The `Text` component is used to display content on your page. + - +## Usage + + ## API reference @@ -103,3 +86,7 @@ You can also use the `variant` prop to change the appearance of the text based on the screen size. + + + + diff --git a/docs-ui/src/content/components/tooltip.mdx b/docs-ui/src/content/components/tooltip.mdx index 4c231aab49..dd2add5308 100644 --- a/docs-ui/src/content/components/tooltip.mdx +++ b/docs-ui/src/content/components/tooltip.mdx @@ -8,11 +8,14 @@ import { tooltipTriggerPropDefs, tooltipPropDefs, } from './tooltip.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Tooltip - -A tooltip displays a description of an element on hover or focus. + - +## Usage + + ## API reference @@ -40,3 +41,7 @@ The trigger will wrap both the trigger and the tooltip. The tooltip will wrap the content of the tooltip. + + + + diff --git a/docs-ui/src/css/globals.css b/docs-ui/src/css/globals.css index f8ac189113..334e6a870a 100644 --- a/docs-ui/src/css/globals.css +++ b/docs-ui/src/css/globals.css @@ -2,7 +2,8 @@ --bg: #f8f8f8; --panel: #fff; --primary: #000; - --secondary: #757575; + --secondary: #929292; + --action: #fff; --link: #4f5ce0; --font-regular: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', @@ -17,17 +18,16 @@ [data-theme='dark'] { --bg: #000; - --panel: #1a1a1a; + --panel: #181818; --primary: #fff; --secondary: #818181; + --action: #202020; --link: #4856e8; --border: #2a2a2a; --surface-1: #282828; } body { - display: flex; - flex-direction: row; background-color: var(--bg); color: var(--primary); font-family: var(--font-regular); diff --git a/docs-ui/src/css/mdx.module.css b/docs-ui/src/css/mdx.module.css index 61c0208865..6a65995003 100644 --- a/docs-ui/src/css/mdx.module.css +++ b/docs-ui/src/css/mdx.module.css @@ -1,8 +1,9 @@ .h1 { - font-size: 2.5rem; - margin-top: 4rem; - margin-bottom: 1.5rem; + font-size: 3rem; + margin-top: 0; + margin-bottom: 1.25rem; color: var(--primary); + font-weight: 400; } .h2 { @@ -10,6 +11,7 @@ margin-top: 3rem; margin-bottom: 1.5rem; color: var(--primary); + font-weight: 400; } .h3 { @@ -17,6 +19,7 @@ margin-top: 2rem; margin-bottom: 0.5rem; color: var(--primary); + font-weight: 400; } .p { diff --git a/docs-ui/src/css/page.module.css b/docs-ui/src/css/page.module.css deleted file mode 100644 index 3a0bb492d8..0000000000 --- a/docs-ui/src/css/page.module.css +++ /dev/null @@ -1,50 +0,0 @@ -.global { - width: 100%; -} - -.container { - width: 100%; - padding: 0 16px; -} - -@media (min-width: 768px) { - .container { - padding-left: 332px; - } -} - -.page { - flex: 1; -} - -.page p { - color: var(--primary); - font-size: 1rem; - line-height: 1.5rem; - margin-top: 0; - margin-bottom: 1rem; -} - -.content { - width: 100%; - max-width: 960px; - margin: 0 auto; - padding: 64px 24px 64px; -} - -.header { - width: 100%; - margin-bottom: 64px; -} - -.header img { - width: 100%; - height: auto; -} - -.pageContainer { - width: 100%; - max-width: 960px; - margin: 0 auto; - padding: 0 24px 64px; -} diff --git a/docs-ui/yarn.lock b/docs-ui/yarn.lock index 7fbef814a6..22531f1a3b 100644 --- a/docs-ui/yarn.lock +++ b/docs-ui/yarn.lock @@ -907,6 +907,15 @@ __metadata: languageName: node linkType: hard +"@remixicon/react@npm:^4.6.0": + version: 4.6.0 + resolution: "@remixicon/react@npm:4.6.0" + peerDependencies: + react: ">=18.2.0" + checksum: 10/d92c8131734062fca8d82d55dd8f3f4e28cb72c5eefda528c8aba5f3298f0c847342eecdbd7787d6da5af21b699d735f6cf27f151f0c982c1c9f06a78b235c94 + languageName: node + linkType: hard + "@rtsao/scc@npm:^1.1.0": version: 1.1.0 resolution: "@rtsao/scc@npm:1.1.0" @@ -2302,6 +2311,7 @@ __metadata: "@mdx-js/loader": "npm:^3.1.0" "@mdx-js/react": "npm:^3.1.0" "@next/mdx": "npm:15.3.4" + "@remixicon/react": "npm:^4.6.0" "@storybook/react": "npm:^8.6.8" "@types/mdx": "npm:^2.0.13" "@types/node": "npm:^20" @@ -2321,7 +2331,6 @@ __metadata: prop-types: "npm:^15.8.1" react: "npm:19.1.0" react-dom: "npm:19.1.0" - react-frame-component: "npm:^5.2.7" shiki: "npm:^1.26.1" storybook: "npm:^8.6.8" typescript: "npm:^5" @@ -5587,17 +5596,6 @@ __metadata: languageName: node linkType: hard -"react-frame-component@npm:^5.2.7": - version: 5.2.7 - resolution: "react-frame-component@npm:5.2.7" - peerDependencies: - prop-types: ^15.5.9 - react: ">= 16.3" - react-dom: ">= 16.3" - checksum: 10/bedee0c25075cbfb6d592991937640a9c8680f9017284e73ced338cc07348b3efc99121052b60ba0923d3d4ec1ed050348ddec452f059e0b947f6bff1d5bfe24 - languageName: node - linkType: hard - "react-is@npm:^16.13.1": version: 16.13.1 resolution: "react-is@npm:16.13.1" diff --git a/microsite/data/plugins/signadot.yaml b/microsite/data/plugins/signadot.yaml new file mode 100644 index 0000000000..9b6b162901 --- /dev/null +++ b/microsite/data/plugins/signadot.yaml @@ -0,0 +1,10 @@ +--- +title: Signadot +author: Signadot +authorUrl: https://www.signadot.com/ +category: Kubernetes +description: View and manage lightweight ephemeral Kubernetes environments (Sandboxes) for testing microservices in pull requests and local development. +documentation: https://github.com/signadot/backstage-plugin +iconUrl: https://avatars.githubusercontent.com/u/57196635 +npmPackageName: '@signadot-lib/backstage-plugin' +addedDate: '2025-07-09' diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 690b469df3..163ade002e 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -29,7 +29,7 @@ import { Table as Table_2 } from '@tanstack/react-table'; import type { TabListProps as TabListProps_2 } from 'react-aria-components'; import type { TabPanelProps as TabPanelProps_2 } from 'react-aria-components'; import { TabProps } from 'react-aria-components'; -import type { TabsProps as TabsProps_2 } from 'react-aria-components'; +import { TabsProps as TabsProps_2 } from 'react-aria-components'; import { TdHTMLAttributes } from 'react'; import type { TextFieldProps as TextFieldProps_2 } from 'react-aria-components'; import { ThHTMLAttributes } from 'react'; @@ -1021,6 +1021,8 @@ export interface HeaderProps { // (undocumented) menuItems?: HeaderMenuItem[]; // (undocumented) + onTabSelectionChange?: TabsProps_2['onSelectionChange']; + // (undocumented) tabs?: HeaderTab[]; // (undocumented) title?: string; diff --git a/packages/ui/src/components/Header/Header.tsx b/packages/ui/src/components/Header/Header.tsx index e6aae08eeb..4f85d9096d 100644 --- a/packages/ui/src/components/Header/Header.tsx +++ b/packages/ui/src/components/Header/Header.tsx @@ -50,7 +50,7 @@ export const Header = (props: HeaderProps) => { /> {tabs && (
- + {tabs?.map(tab => ( diff --git a/packages/ui/src/components/Header/types.ts b/packages/ui/src/components/Header/types.ts index e1156c9a55..cf76929389 100644 --- a/packages/ui/src/components/Header/types.ts +++ b/packages/ui/src/components/Header/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TabsProps } from 'react-aria-components'; + /** * Props for the main Header component. * @@ -26,6 +28,7 @@ export interface HeaderProps { customActions?: React.ReactNode; menuItems?: HeaderMenuItem[]; tabs?: HeaderTab[]; + onTabSelectionChange?: TabsProps['onSelectionChange']; } /** diff --git a/packages/ui/src/components/Menu/Menu.stories.tsx b/packages/ui/src/components/Menu/Menu.stories.tsx index 578ce945ae..c135b6f3f9 100644 --- a/packages/ui/src/components/Menu/Menu.stories.tsx +++ b/packages/ui/src/components/Menu/Menu.stories.tsx @@ -40,34 +40,33 @@ const options = [ ]; export const Default: Story = { - args: { - children: ( - <> - ( - - )} - /> - - - - Settings - Invite new members - Download app - Log out - - - - - ), - }, + args: { children: undefined }, + render: args => ( + + ( + + )} + /> + + + + Settings + Invite new members + Download app + Log out + + + + + ), }; export const Open: Story = { @@ -75,6 +74,7 @@ export const Open: Story = { ...Default.args, open: true, }, + render: Default.render, }; export const OpenOnHover: Story = { @@ -82,49 +82,49 @@ export const OpenOnHover: Story = { ...Default.args, openOnHover: true, }, + render: Default.render, }; export const Submenu: Story = { - args: { - children: ( - <> - ( - - )} - /> - - - - Settings - Invite new members - Download app - Log out - - Submenu - - - - Submenu Item 1 - Submenu Item 2 - Submenu Item 3 - - - - - - - - - ), - }, + args: { children: undefined }, + render: args => ( + + ( + + )} + /> + + + + Settings + Invite new members + Download app + Log out + + Submenu + + + + Submenu Item 1 + Submenu Item 2 + Submenu Item 3 + + + + + + + + + ), }; export const SubmenuCombobox = () => { diff --git a/plugins/catalog-react/report-alpha.api.md b/plugins/catalog-react/report-alpha.api.md index 8476eed695..1db7a8e5e4 100644 --- a/plugins/catalog-react/report-alpha.api.md +++ b/plugins/catalog-react/report-alpha.api.md @@ -372,18 +372,43 @@ export const EntityHeaderBlueprint: ExtensionBlueprint<{ name: undefined; params: { loader: () => Promise; + filter?: EntityPredicate | ((entity: Entity) => boolean); }; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - { - optional: true; - } - >; + output: + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + { + optional: true; + } + >; inputs: {}; - config: {}; - configInput: {}; + config: { + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + }; dataRefs: { + filterFunction: ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + {} + >; element: ConfigurableExtensionDataRef< JSX_2.Element, 'core.reactElement', diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityHeaderBlueprint.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityHeaderBlueprint.tsx index 431197e753..b83136dcd9 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityHeaderBlueprint.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityHeaderBlueprint.tsx @@ -19,22 +19,44 @@ import { coreExtensionData, ExtensionBoundary, } from '@backstage/frontend-plugin-api'; +import { EntityPredicate } from '../predicates/types'; +import { Entity } from '@backstage/catalog-model'; +import { resolveEntityFilterData } from './resolveEntityFilterData'; +import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema'; +import { + entityFilterExpressionDataRef, + entityFilterFunctionDataRef, +} from './extensionData'; /** @alpha */ export const EntityHeaderBlueprint = createExtensionBlueprint({ kind: 'entity-header', - attachTo: { id: 'page:catalog/entity', input: 'header' }, + attachTo: { id: 'page:catalog/entity', input: 'headers' }, dataRefs: { + filterFunction: entityFilterFunctionDataRef, element: coreExtensionData.reactElement, }, - output: [coreExtensionData.reactElement.optional()], + config: { + schema: { + filter: z => createEntityPredicateSchema(z).optional(), + }, + }, + output: [ + entityFilterFunctionDataRef.optional(), + entityFilterExpressionDataRef.optional(), + coreExtensionData.reactElement.optional(), + ], *factory( params: { loader: () => Promise; + filter?: EntityPredicate | ((entity: Entity) => boolean); }, - { node }, + { node, config }, ) { - const { loader } = params; + const { loader, filter } = params; + + yield* resolveEntityFilterData(filter, config, node); + if (loader) { yield coreExtensionData.reactElement( ExtensionBoundary.lazy(node, loader), diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md index 4e00a5185e..81ebd10c3b 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -1097,17 +1097,24 @@ const _default: FrontendPlugin< } >; inputs: { - header: ExtensionInput< - ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - { - optional: true; - } - >, + headers: ExtensionInput< + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + { + optional: true; + } + >, { - singleton: true; - optional: true; + singleton: false; + optional: false; } >; contents: ExtensionInput< diff --git a/plugins/catalog/src/alpha/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/alpha/components/EntityLayout/EntityLayout.tsx index 750c8567ee..8f5838d545 100644 --- a/plugins/catalog/src/alpha/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/alpha/components/EntityLayout/EntityLayout.tsx @@ -102,13 +102,21 @@ export const EntityLayout = (props: EntityLayoutProps) => { UNSTABLE_contextMenuOptions, contextMenuItems, children, - header, NotFoundComponent, parentEntityRelations, } = props; const { kind } = useRouteRefParams(entityRouteRef); const { entity, loading, error } = useAsyncEntity(); + const header = props.header ?? ( + + ); + const routes = useElementFilter( children, elements => @@ -142,14 +150,7 @@ export const EntityLayout = (props: EntityLayoutProps) => { return ( - {header ?? ( - - )} + {!loading && header} {loading && } diff --git a/plugins/catalog/src/alpha/filters.tsx b/plugins/catalog/src/alpha/filters.tsx index fcfbc394f2..d72e299fbb 100644 --- a/plugins/catalog/src/alpha/filters.tsx +++ b/plugins/catalog/src/alpha/filters.tsx @@ -133,13 +133,14 @@ const catalogListCatalogFilter = CatalogFilterBlueprint.makeWithOverrides({ }, }); +// this is the default order that the filters will be applied in export default [ - catalogTagCatalogFilter, catalogKindCatalogFilter, catalogTypeCatalogFilter, - catalogModeCatalogFilter, - catalogNamespaceCatalogFilter, - catalogLifecycleCatalogFilter, - catalogProcessingStatusCatalogFilter, catalogListCatalogFilter, + catalogModeCatalogFilter, + catalogLifecycleCatalogFilter, + catalogTagCatalogFilter, + catalogProcessingStatusCatalogFilter, + catalogNamespaceCatalogFilter, ]; diff --git a/plugins/catalog/src/alpha/pages.tsx b/plugins/catalog/src/alpha/pages.tsx index 81ec0c5cb9..f0c5316a84 100644 --- a/plugins/catalog/src/alpha/pages.tsx +++ b/plugins/catalog/src/alpha/pages.tsx @@ -79,10 +79,10 @@ export const catalogPage = PageBlueprint.makeWithOverrides({ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ name: 'entity', inputs: { - header: createExtensionInput( - [EntityHeaderBlueprint.dataRefs.element.optional()], - { singleton: true, optional: true }, - ), + headers: createExtensionInput([ + EntityHeaderBlueprint.dataRefs.element.optional(), + EntityHeaderBlueprint.dataRefs.filterFunction.optional(), + ]), contents: createExtensionInput([ coreExtensionData.reactElement, coreExtensionData.routePath, @@ -124,9 +124,19 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ { title: string; items: Array<(typeof inputs.contents)[0]> } >; - const header = inputs.header?.get( - EntityHeaderBlueprint.dataRefs.element, - ); + // Get available headers, sorted by if they have a filter function or not. + // TODO(blam): we should really have priority or some specificity here which can be used to sort the headers. + // That can be done with embedding the priority in the dataRef alongside the filter function. + const headers = inputs.headers + .map(header => ({ + element: header.get(EntityHeaderBlueprint.dataRefs.element), + filter: header.get(EntityHeaderBlueprint.dataRefs.filterFunction), + })) + .sort((a, b) => { + if (a.filter && !b.filter) return -1; + if (!a.filter && b.filter) return 1; + return 0; + }); let groups = Object.entries(defaultEntityContentGroups).reduce( (rest, group) => { @@ -169,6 +179,10 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ ? menuItems.filter(i => i.filter(entity)).map(i => i.element) : []; + const header = headers.find( + h => !h.filter || h.filter(entity!), + )?.element; + return (