diff --git a/docs-ui/src/app/hooks/page.mdx b/docs-ui/src/app/hooks/page.mdx index b2e2b81e7d..75bee70743 100644 --- a/docs-ui/src/app/hooks/page.mdx +++ b/docs-ui/src/app/hooks/page.mdx @@ -18,17 +18,7 @@ Backstage UI custom hooks provide easy access to design system features, style m /> - - diff --git a/docs-ui/src/app/hooks/use-media-query/page.mdx b/docs-ui/src/app/hooks/use-media-query/page.mdx index 84668b2a3e..bd01152581 100644 --- a/docs-ui/src/app/hooks/use-media-query/page.mdx +++ b/docs-ui/src/app/hooks/use-media-query/page.mdx @@ -3,6 +3,7 @@ import { CodeBlock } from '@/components/CodeBlock'; import { PageTitle } from '@/components/PageTitle'; import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; +import { Banner } from '@/components/Banner'; import { UseMediaQueryOrientationExample, UseMediaQueryPreferencesExample, @@ -25,6 +26,11 @@ import { description="Hook to evaluate and react to CSS media queries programmatically." /> + + ## Usage The `useMediaQuery` hook allows you to evaluate CSS media queries in your components, enabling responsive design and behavior. diff --git a/docs-ui/src/app/hooks/use-styles/page.mdx b/docs-ui/src/app/hooks/use-styles/page.mdx new file mode 100644 index 0000000000..ca235c4778 --- /dev/null +++ b/docs-ui/src/app/hooks/use-styles/page.mdx @@ -0,0 +1,59 @@ +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; +import { Banner } from '@/components/Banner'; +import { PageTitle } from '@/components/PageTitle'; +import { PropsTable } from '@/components/PropsTable'; +import { + useStylesUsageSnippet, + useStylesDefsSnippet, + useStylesAttributesSnippet, +} from './snippets'; +import { useStylesPropsDefs, useStylesReturnDefs } from './props-definition'; + + + + + +## Usage + +The `useStyles` hook is the core styling utility that processes component definitions and props to generate: + +- **Class names** +- **Data attributes** for state-based styling (e.g., `data-disabled`, `data-variant`) +- **Utility classes** from utility props like `padding`, `margin`, `display` (with responsive support) +- **CSS custom properties** for custom values not in predefined utility lists +- **Cleaned props** with utility props removed (safe to spread onto DOM elements) + + + +## Parameters + + +## Return Value + + + +## How It Works + +### Component Definition + +A component definition describes how a component should be styled: + + + + + +### Data Attributes + +Data attributes enable state-based styling through CSS selectors. The hook automatically adds `data-*` to the attributes based on the defined props: + + + + + diff --git a/docs-ui/src/app/hooks/use-styles/props-definition.ts b/docs-ui/src/app/hooks/use-styles/props-definition.ts new file mode 100644 index 0000000000..0c8a2a1728 --- /dev/null +++ b/docs-ui/src/app/hooks/use-styles/props-definition.ts @@ -0,0 +1,58 @@ +import { type PropDef } from '@/utils/propDefs'; + +export const useStylesPropsDefs: Record = { + componentDefinition: { + type: 'complex', + complexType: { + name: 'ComponentDefinition', + properties: { + classNames: { + type: 'Record', + required: true, + }, + dataAttributes: { + type: 'Record', + required: false, + }, + utilityProps: { + type: 'string[]', + required: false, + }, + }, + }, + }, + props: { + type: 'enum', + description: 'All component props', + values: ['{ [key: string]: any; }'], + }, +}; + +export const useStylesReturnDefs: Record = { + classNames: { + type: 'enum', + values: ['Record'], + description: + "The component's class names mapped to their style definitions", + }, + dataAttributes: { + type: 'enum', + values: ['Record'], + description: + 'Data attributes generated from the component definition and props', + }, + utilityClasses: { + type: 'string', + description: 'Combined utility classes based on utility props', + }, + style: { + type: 'enum', + values: ['React.CSSProperties'], + description: 'The combined style object for the component', + }, + cleanedProps: { + type: 'enum', + values: ['Record'], + description: 'The original props with utility props removed', + }, +}; diff --git a/docs-ui/src/app/hooks/use-styles/snippets.ts b/docs-ui/src/app/hooks/use-styles/snippets.ts new file mode 100644 index 0000000000..55cf7ddee7 --- /dev/null +++ b/docs-ui/src/app/hooks/use-styles/snippets.ts @@ -0,0 +1,105 @@ +export const useStylesUsageSnippet = `import { useStyles } from '@backstage/ui'; +import type { + ComponentDefinition, + SpaceProps, + UtilityProps, +} from '@backstage/ui'; + +// Define your component's styling configuration +const buttonDefinition: ComponentDefinition = { + classNames: { + root: 'bui-button', + icon: 'bui-button-icon', + }, + dataAttributes: { + variant: ['primary', 'secondary', 'ghost'] as const, + size: ['small', 'medium', 'large'] as const, + 'is-disabled': [true, false] as const, + }, + utilityProps: ['gap', 'mb', 'mt', 'ml', 'mr'], +} as const satisfies ComponentDefinition; + +// type the component props +interface ButtonProps { + variant?: 'primary' | 'secondary' | 'ghost'; + size?: 'small' | 'medium' | 'large'; + isDisabled?: boolean; + gap?: UtilityProps['gap']; + mb?: SpaceProps['mb']; + mt?: SpaceProps['mt']; + ml?: SpaceProps['ml']; + mr?: SpaceProps['mr']; + icon?: React.ReactElement; + children?: React.ReactNode; +} + +// Use the useStyles hook in your component +export function Button({ + variant = 'primary', + size = 'medium', + isDisabled, + ...props +}: Readonly) { + const { classNames, dataAttributes, utilityClasses, cleanedProps } = + useStyles(buttonDefinition, { + variant, + size, + 'is-disabled': isDisabled, + ...props, + }); + + return ( + + ); +}`; + +export const useStylesDefsSnippet = `const componentDefinition = { + // CSS class names for component parts + classNames: { + root: 'bui-button', + icon: 'bui-button-icon', + }, + + // Props that become data attributes for CSS targeting + // data-attribute must be kebab-case to respect HTML standards + // And casing matters here, lowercase only: + // 'variant' not 'Variant', 'is-disabled' not 'isDisabled'.. + dataAttributes: { + variant: ['primary', 'secondary'] as const, + size: ['small', 'large'] as const, + 'is-disabled': [true, false] as const, + }, + + // Props that become utility classes (spacing, layout, etc.) + utilityProps: ['m', 'mb', 'mt', 'ml', 'mr'], +};`; + +export const useStylesAttributesSnippet = `/* Component CSS can use data attributes for variants */ +.bui-button { + padding: 0.5rem 1rem; + border-radius: 0.25rem; +} + +/* Variant styles */ +.bui-button[data-variant="primary"] { + background: blue; + color: white; +} + +.bui-button[data-variant="secondary"] { + background: gray; + color: black; +} + +/* State styles */ +.bui-button[data-is-disabled="true"] { + opacity: 0.5; + pointer-events: none; +}`;