Improve page title

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-07-21 10:32:28 +01:00
parent eb479117f1
commit f40a5d11a2
35 changed files with 238 additions and 225 deletions
@@ -0,0 +1,29 @@
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 (
<MDXRemote
components={formattedMDXComponents}
source={`${componentChangelog
?.map(change => {
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')}`}
/>
);
};
@@ -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;
}
@@ -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 (
<div className={styles.container}>
<div className={styles.component}>{type}</div>
<MDXRemote
components={formattedMDXComponents}
source={`# ${title}\n\n${description}`}
/>
</div>
);
};
@@ -73,11 +73,12 @@
}
.sectionTitle {
font-size: 0.875rem;
font-weight: 600;
padding: 12px 0;
color: var(--primary);
margin-top: 24px;
font-size: 0.6875rem;
font-weight: 500;
padding: 0 12px 4px;
color: var(--secondary);
margin-top: 40px;
text-transform: uppercase;
}
.line {
@@ -93,13 +94,12 @@
border-radius: 4px;
&:hover {
background-color: var(--surface-1);
transition: background-color 0.2s ease-in-out;
background-color: white;
}
}
.line.active {
background-color: var(--surface-1);
background-color: white;
}
.line.active .lineTitle {
@@ -109,7 +109,7 @@
.lineTitle {
font-size: 14px;
font-weight: 400;
color: var(--secondary);
color: var(--primary);
}
.lineStatus {
@@ -14,14 +14,14 @@
.left {
display: flex;
align-items: center;
gap: 0.75rem;
gap: 0.5rem;
}
.name {
margin-top: 3px;
font-size: 28px;
color: #26c9ad;
font-weight: 600;
font-weight: 500;
}
.actions {
+15 -2
View File
@@ -1,9 +1,22 @@
'use client';
import { ThemeSelector } from './theme';
import { ThemeNameSelector } from './theme-name';
import styles from './Toolbar.module.css';
import { Nav } from './nav';
import { usePathname } from 'next/navigation';
import { components, layoutComponents } from '@/utils/data';
import { AnimatedName } from './AnimatedName';
export const Toolbar = () => {
const pathname = usePathname();
const allComponents = [...layoutComponents, ...components];
const isComponent = pathname.startsWith('/components');
const componentSlug = pathname.replace('/components/', '');
const findComponent = allComponents.find(c => c.slug === componentSlug);
let name = 'UI';
if (isComponent && findComponent) name = findComponent.title;
return (
<div className={styles.toolbar}>
<div className={styles.left}>
@@ -19,7 +32,7 @@ export const Toolbar = () => {
fill="#121212"
/>
</svg>
<div className={styles.name}>Button</div>
<div className={styles.name}>UI</div>
</div>
<div className={styles.actions}>
<ThemeNameSelector />
@@ -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;
}
-49
View File
@@ -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 (
<Tabs.Root
className={styles.tabs}
value={pathname.includes('playground') ? 'playground' : 'docs'}
onValueChange={onValueChange}
>
<Tabs.List className={styles.list}>
<Tabs.Tab
className={styles.tab}
value="docs"
onClick={() => {
router.push('/');
}}
>
Documentation
</Tabs.Tab>
<Tabs.Tab
className={styles.tab}
value="playground"
onClick={() => {
router.push('/playground');
}}
>
Playground
</Tabs.Tab>
<Tabs.Indicator className={styles.indicator} />
</Tabs.List>
</Tabs.Root>
);
};
+5 -3
View File
@@ -9,10 +9,12 @@ import {
snippetFallback,
} from './avatar.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Avatar
An avatar component with a fallback for initials.
<PageTitle
title="Avatar"
description="An avatar component with a fallback for initials."
/>
<Snippet
align="center"
+6 -3
View File
@@ -11,10 +11,13 @@ import {
} from './box.props';
import { spacingPropDefs } from '@/utils/propDefs';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Box
Box is the lowest-level component in Backstage UI. It provides a consistent API for styling and layout.
<PageTitle
type="Layout"
title="Box"
description="Box is the lowest-level component in Backstage UI. It provides a consistent API for styling and layout."
/>
<Snippet
py={4}
@@ -13,10 +13,12 @@ import {
buttonIconAsLinkSnippet,
} from './button-icon.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# ButtonIcon
A button component with a single icon that can be used to trigger actions.
<PageTitle
title="ButtonIcon"
description="A button component with a single icon that can be used to trigger actions."
/>
<Snippet
align="center"
@@ -12,10 +12,12 @@ import {
buttonLinkResponsiveSnippet,
} from './button-link.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# ButtonLink
A button component that can be used as a link.
<PageTitle
title="ButtonLink"
description="A button component that can be used as a link."
/>
<Snippet
align="center"
+17 -9
View File
@@ -12,11 +12,13 @@ import {
buttonResponsiveSnippet,
buttonAsLinkSnippet,
} from './button.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { ChangelogComponent } from '@/components/ChangelogComponent';
import { PageTitle } from '@/components/PageTitle';
# Button
A button component that can be used to trigger actions.
<PageTitle
title="Button"
description="A button component that can be used to trigger actions."
/>
<Snippet
align="center"
@@ -25,11 +27,9 @@ A button component that can be used to trigger actions.
code={buttonVariantsSnippet}
/>
<ComponentInfos
component="button"
classNames={['bui-Button']}
usageCode={buttonSnippetUsage}
/>
## Usage
<CodeBlock code={buttonSnippetUsage} />
## API reference
@@ -102,3 +102,11 @@ If you want to use a button as a link, please use the `ButtonLink` component.
preview={<ButtonLinkSnippet story="Variants" />}
code={buttonAsLinkSnippet}
/>
## Theming
- `bui-Button`
## Changelog
<ChangelogComponent component="button" />
+5 -3
View File
@@ -13,10 +13,12 @@ import {
cardListRowSnippet,
} from './card.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Card
A card component that can be used to display content in a box.
<PageTitle
title="Card"
description="A card component that can be used to display content in a box."
/>
<Snippet
align="center"
+5 -3
View File
@@ -9,10 +9,12 @@ import {
checkboxVariantsSnippet,
} from './checkbox.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Checkbox
A checkbox component that can be used to trigger actions.
<PageTitle
title="Checkbox"
description="A checkbox component that can be used to trigger actions."
/>
<Snippet
align="center"
@@ -12,10 +12,12 @@ import {
collapsibleOpenSnippet,
} from './collapsible.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Collapsible
An avatar component with a fallback for initials.
<PageTitle
title="Collapsible"
description="A collapsible component that can be used to display content in a box."
/>
<Snippet
align="center"
+6 -4
View File
@@ -10,11 +10,13 @@ import {
containerResponsiveSnippet,
} from './container.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Container
The container component let you use our default max-width and center the
content on the page.
<PageTitle
type="Layout"
title="Container"
description="The container component let you use our default max-width and center the content on the page."
/>
<Snippet
py={4}
+6 -3
View File
@@ -13,10 +13,13 @@ import {
} from './flex.props';
import { spacingPropDefs } from '@/utils/propDefs';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Flex
A responsive flex container component for vertical stacking with customizable gaps.
<PageTitle
type="Layout"
title="Flex"
description="A responsive flex container component for vertical stacking with customizable gaps."
/>
<Snippet
py={4}
+6 -4
View File
@@ -15,11 +15,13 @@ import {
gridStartEndSnippet,
} from './grid.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Grid
A layout component that helps to create simple column-based layouts as well as
more complex ones.
<PageTitle
type="Layout"
title="Grid"
description="A layout component that helps to create simple column-based layouts as well as more complex ones."
/>
<Snippet
py={4}
+5 -3
View File
@@ -11,10 +11,12 @@ import {
headingResponsiveSnippet,
} from './heading.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Heading
Headings are used to structure the content of your page.
<PageTitle
title="Heading"
description="Headings are used to structure the content of your page."
/>
<Snippet
py={4}
+5 -3
View File
@@ -8,10 +8,12 @@ import {
iconDefaultSnippet,
} from './icon.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Icon
Icons are used to represent an action or a state.
<PageTitle
title="Icon"
description="Icons are used to represent an action or a state."
/>
<Snippet
py={4}
+5 -3
View File
@@ -14,10 +14,12 @@ import {
linkWeightsSnippet,
} from './link.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Link
A link component that renders a `<a>` element.
<PageTitle
title="Link"
description="A link component that renders a `<a>` element."
/>
<Snippet
align="center"
+5 -3
View File
@@ -14,10 +14,12 @@ import {
menuSubmenuSnippet,
} from './menu.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Menu
A list of actions in a dropdown, enhanced with keyboard navigation.
<PageTitle
title="Menu"
description="A list of actions in a dropdown, enhanced with keyboard navigation."
/>
<Snippet
align="center"
@@ -13,10 +13,12 @@ import {
radioGroupReadOnlySnippet,
} from './radio-group.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# RadioGroup
A radio group allows a user to select a single item from a list of mutually exclusive options.
<PageTitle
title="RadioGroup"
description="A radio group allows a user to select a single item from a list of mutually exclusive options."
/>
<Snippet
align="center"
@@ -10,9 +10,10 @@ import {
} from './search-field.props';
import { ComponentInfos } from '@/components/ComponentInfos';
# SearchField
A SearchField is a text field designed for searches.
<PageTitle
title="SearchField"
description="A SearchField is a text field designed for searches."
/>
<Snippet
align="center"
+5 -3
View File
@@ -13,10 +13,12 @@ import {
selectIconSnippet,
} from './select.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Select
A common form component for choosing a predefined value in a dropdown menu.
<PageTitle
title="Select"
description="A common form component for choosing a predefined value in a dropdown menu."
/>
<Snippet
align="center"
+5 -3
View File
@@ -10,10 +10,12 @@ import {
skeletonDemo2Snippet,
} from './skeleton.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Skeleton
Use to show a placeholder while content is loading.
<PageTitle
title="Skeleton"
description="Use to show a placeholder while content is loading."
/>
<Snippet
align="center"
+5 -3
View File
@@ -4,10 +4,12 @@ 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';
# Switch
A control that indicates whether a setting is on or off.
<PageTitle
title="Switch"
description="A control that indicates whether a setting is on or off."
/>
<Snippet
align="center"
+6 -1
View File
@@ -1 +1,6 @@
# Table (Coming soon)
import { PageTitle } from '@/components/PageTitle';
<PageTitle
title="Table"
description="A table component that can be used to display data in a grid. (Coming soon)"
/>
+5 -3
View File
@@ -11,10 +11,12 @@ import {
tabsDefaultSnippet,
} from './tabs.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Tabs
A component for toggling between related panels on the same page.
<PageTitle
title="Tabs"
description="A component for toggling between related panels on the same page."
/>
<Snippet
py={4}
@@ -9,10 +9,12 @@ import {
textFieldDescriptionSnippet,
} from './text-field.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# TextField
A text field component for your forms.
<PageTitle
title="TextField"
description="A text field component for your forms."
/>
<Snippet
align="center"
+5 -3
View File
@@ -12,10 +12,12 @@ import {
textResponsiveSnippet,
} from './text.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Text
The `Text` component is used to display content on your page.
<PageTitle
title="Text"
description="The `Text` component is used to display content on your page."
/>
<Snippet
py={4}
+5 -3
View File
@@ -9,10 +9,12 @@ import {
tooltipPropDefs,
} from './tooltip.props';
import { ComponentInfos } from '@/components/ComponentInfos';
import { PageTitle } from '@/components/PageTitle';
# Tooltip
A tooltip displays a description of an element on hover or focus.
<PageTitle
title="Tooltip"
description="A tooltip displays a description of an element on hover or focus."
/>
<Snippet
py={4}
+1 -1
View File
@@ -2,7 +2,7 @@
--bg: #f8f8f8;
--panel: #fff;
--primary: #000;
--secondary: #757575;
--secondary: #929292;
--link: #4f5ce0;
--font-regular: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
+6 -3
View File
@@ -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 {