diff --git a/docs-ui/src/app/hooks/page.mdx b/docs-ui/src/app/hooks/page.mdx new file mode 100644 index 0000000000..ea73f07ce3 --- /dev/null +++ b/docs-ui/src/app/hooks/page.mdx @@ -0,0 +1,7 @@ +import { HookGrid } from '@/components/HookGrid'; + +# Hooks + +Backstage UI custom hooks provide easy access to design system features, style management, and responsive interface creation. + + diff --git a/docs-ui/src/app/hooks/use-breakpoint/components.tsx b/docs-ui/src/app/hooks/use-breakpoint/components.tsx new file mode 100644 index 0000000000..c58010c66f --- /dev/null +++ b/docs-ui/src/app/hooks/use-breakpoint/components.tsx @@ -0,0 +1,17 @@ +'use client'; + +import { useBreakpoint } from '@backstage/ui'; + +export function UseBreakpointExample() { + const { breakpoint, up, down } = useBreakpoint(); + + return ( +
+

Current Breakpoint: {breakpoint}

+ {(up('md') &&

The viewport is larger than 1024px.

) || + (down('sm') &&

The viewport is smaller than 768px.

) || ( +

The viewport is between 768px and 1024px.

+ )} +
+ ); +} diff --git a/docs-ui/src/app/hooks/use-breakpoint/page.mdx b/docs-ui/src/app/hooks/use-breakpoint/page.mdx new file mode 100644 index 0000000000..f7f3378a41 --- /dev/null +++ b/docs-ui/src/app/hooks/use-breakpoint/page.mdx @@ -0,0 +1,47 @@ +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { PageTitle } from '@/components/PageTitle'; +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { Banner } from '@/components/Banner'; +import { useBreakpointReturnDefs } from './props-definition'; +import { useBreakpointExampleSnippet } from './snippets'; +import { UseBreakpointExample } from './components'; + + + +## Usage + + + +The `useBreakpoint` hook returns the active breakpoint and two functions, `up` and `down`, to check if the viewport is above, or below a given breakpoint, letting you adjust your UI responsively. + +} + code={useBreakpointExampleSnippet} +/> + +## Breakpoints + +The default breakpoints are: + +- **initial**: < 640px +- **xs**: ≥ 640px < 768px +- **sm**: ≥ 768px < 1024px +- **md**: ≥ 1024px < 1280px +- **lg**: ≥ 1280px < 1536px +- **xl**: ≥ 1536px + +## Return Value + + + + diff --git a/docs-ui/src/app/hooks/use-breakpoint/props-definition.ts b/docs-ui/src/app/hooks/use-breakpoint/props-definition.ts new file mode 100644 index 0000000000..b1702a82cc --- /dev/null +++ b/docs-ui/src/app/hooks/use-breakpoint/props-definition.ts @@ -0,0 +1,23 @@ +import { type PropDef } from '@/utils/propDefs'; + +const Breakpoint = ['"initial"', '"xs"', '"sm"', '"md"', '"lg"', '"xl"']; + +export const useBreakpointReturnDefs: Record = { + breakpoint: { + type: 'enum', + values: Breakpoint, + description: 'The current active breakpoint based on screen width', + }, + up: { + type: 'enum', + values: [`(breakpoint: ${Breakpoint.join(' | ')}) => boolean`], + description: + 'Function that takes a breakpoint and returns true if the screen width is at or above that breakpoint', + }, + down: { + type: 'enum', + values: [`(breakpoint: ${Breakpoint.join(' | ')}) => boolean`], + description: + 'Function that takes a breakpoint and returns true if the screen width is at or below that breakpoint', + }, +}; diff --git a/docs-ui/src/app/hooks/use-breakpoint/snippets.ts b/docs-ui/src/app/hooks/use-breakpoint/snippets.ts new file mode 100644 index 0000000000..19e6debe65 --- /dev/null +++ b/docs-ui/src/app/hooks/use-breakpoint/snippets.ts @@ -0,0 +1,13 @@ +export const useBreakpointExampleSnippet = `import { useBreakpoint } from '@backstage/ui'; + +function ResponsiveComponent() { + const { breakpoint, up, down } = useBreakpoint(); + + return ( +
+

Current Breakpoint: {breakpoint}

+ {up('md') &&

The viewport is medium or larger.

} + {down('sm') &&

The viewport is small or smaller.

} +
+ ); +}`; diff --git a/docs-ui/src/components/Banner/styles.module.css b/docs-ui/src/components/Banner/styles.module.css index 9666c5d521..d9d3323376 100644 --- a/docs-ui/src/components/Banner/styles.module.css +++ b/docs-ui/src/components/Banner/styles.module.css @@ -16,13 +16,13 @@ .info { background-color: #f2f2f2; border-color: #cdcdcd; - color: #888888; + color: #4b4b4b; } .warning { background-color: #fff2b9; - border-color: #ffd000; - color: #d79927; + border-color: #d79927; + color: #8a3d09; } .icon { diff --git a/docs-ui/src/components/ChangelogComponent/index.tsx b/docs-ui/src/components/ChangelogComponent/index.tsx index 92068d04f8..cca75a9515 100644 --- a/docs-ui/src/components/ChangelogComponent/index.tsx +++ b/docs-ui/src/components/ChangelogComponent/index.tsx @@ -1,16 +1,24 @@ import { changelog } from '@/utils/changelog'; import { MDXRemote } from 'next-mdx-remote-client/rsc'; import { formattedMDXComponents } from '@/mdx-components'; -import type { Component } from '@/utils/changelog'; +import type { AtLeastOne, Component, Hook } from '@/utils/changelog'; import { Badge, BreakingBadge, generateChangelogMarkdown, } from '../Changelog/utils'; -export const ChangelogComponent = ({ component }: { component: Component }) => { - const componentChangelog = changelog.filter(c => - c.components.includes(component), +type ChangelogComponentProps = AtLeastOne<{ + component: Component; + hook: Hook; +}>; + +export const ChangelogComponent = ({ + component, + hook, +}: Readonly) => { + const componentChangelog = changelog.filter( + c => c.components?.includes(component) || c.hooks?.includes(hook), ); const content = `## Changelog diff --git a/docs-ui/src/components/HookGrid/HookGrid.module.css b/docs-ui/src/components/HookGrid/HookGrid.module.css new file mode 100644 index 0000000000..a6dd54a864 --- /dev/null +++ b/docs-ui/src/components/HookGrid/HookGrid.module.css @@ -0,0 +1,29 @@ +.grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + margin-top: 1rem; + margin-bottom: 2rem; +} + +@media (min-width: 768px) { + .grid { + grid-template-columns: repeat(4, 1fr); + } +} + +.item { + display: flex; + align-items: center; + gap: 6px; + padding: 10px 0; + font-size: 14px; + font-weight: 400; + color: var(--primary); + text-decoration: none; + + &:hover { + color: var(--link); + text-decoration: underline; + text-underline-offset: 2px; + } +} diff --git a/docs-ui/src/components/HookGrid/HookGrid.tsx b/docs-ui/src/components/HookGrid/HookGrid.tsx new file mode 100644 index 0000000000..871ee885d2 --- /dev/null +++ b/docs-ui/src/components/HookGrid/HookGrid.tsx @@ -0,0 +1,21 @@ +'use client'; + +import Link from 'next/link'; +import { hooks } from '@/utils/data'; +import styles from './HookGrid.module.css'; + +export const HookGrid = () => { + return ( +
+ {hooks.map(item => ( + + {item.title} + + ))} +
+ ); +}; diff --git a/docs-ui/src/components/HookGrid/index.ts b/docs-ui/src/components/HookGrid/index.ts new file mode 100644 index 0000000000..934f73aacf --- /dev/null +++ b/docs-ui/src/components/HookGrid/index.ts @@ -0,0 +1 @@ +export * from './HookGrid'; diff --git a/docs-ui/src/components/Navigation/Navigation.tsx b/docs-ui/src/components/Navigation/Navigation.tsx index bad77e942e..ac62c03d7a 100644 --- a/docs-ui/src/components/Navigation/Navigation.tsx +++ b/docs-ui/src/components/Navigation/Navigation.tsx @@ -11,7 +11,7 @@ import { RiServiceLine, RiStackLine, } from '@remixicon/react'; -import { components } from '@/utils/data'; +import { components, hooks } from '@/utils/data'; import styles from './Navigation.module.css'; interface NavigationProps { @@ -92,6 +92,29 @@ export const Navigation = ({ onLinkClick }: NavigationProps) => { ); })} +
+ Hooks +
+ {hooks.map(item => { + const isActive = pathname === `/hooks/${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/utils/data.ts b/docs-ui/src/utils/data.ts index 8132bc952a..753473ab64 100644 --- a/docs-ui/src/utils/data.ts +++ b/docs-ui/src/utils/data.ts @@ -138,3 +138,10 @@ export const components: Page[] = [ slug: 'visually-hidden', }, ]; + +export const hooks: Page[] = [ + { + title: 'useBreakpoint', + slug: 'use-breakpoint', + }, +]; diff --git a/docs-ui/src/utils/types.ts b/docs-ui/src/utils/types.ts index 210ff1fc7f..c2c383796f 100644 --- a/docs-ui/src/utils/types.ts +++ b/docs-ui/src/utils/types.ts @@ -35,14 +35,22 @@ export type Component = | 'tooltip' | 'visually-hidden'; +export type Hook = 'use-breakpoint'; + export type Version = `${number}.${number}.${number}`; -export interface ChangelogProps { - components: Component[]; +export type AtLeastOne = K extends string + ? Pick & Partial> + : never; + +export type ChangelogProps = { description: string; version: Version; prs: string[]; breaking?: boolean; commitSha?: string; migration?: string; -} +} & AtLeastOne<{ + components: Component[]; + hooks: Hook[]; +}>;