feat: add useStyles hook documentation & disclaimer for internal hooks
Signed-off-by: Antony Bouyon <antony.bouyon@believe.com>
This commit is contained in:
@@ -18,17 +18,7 @@ Backstage UI custom hooks provide easy access to design system features, style m
|
||||
/>
|
||||
<ComponentCard
|
||||
title="useStyles"
|
||||
description="Main hook to access and manipulate Backstage design system styles."
|
||||
description="Main hook to access and manipulate design system styles."
|
||||
href="/hooks/use-styles"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="useSurface"
|
||||
description="Hook to manage surfaces and elevation levels in the interface."
|
||||
href="/hooks/use-surface"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="useIsomorphicLayoutEffect"
|
||||
description="Isomorphic version of useLayoutEffect that works on both client and server."
|
||||
href="/hooks/use-isomorphic-layout-effect"
|
||||
/>
|
||||
</ComponentCards>
|
||||
|
||||
@@ -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."
|
||||
/>
|
||||
|
||||
<Banner
|
||||
variant="warning"
|
||||
text="This hook is currently intended for internal use within the design system."
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
The `useMediaQuery` hook allows you to evaluate CSS media queries in your components, enabling responsive design and behavior.
|
||||
|
||||
@@ -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';
|
||||
|
||||
<PageTitle
|
||||
title="useStyles"
|
||||
description="React hook to get class names and data attributes for a component with responsive support"
|
||||
/>
|
||||
|
||||
<Banner
|
||||
variant="warning"
|
||||
text="This hook is currently intended for internal use within the design system."
|
||||
/>
|
||||
|
||||
## 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)
|
||||
|
||||
<CodeBlock code={useStylesUsageSnippet} />
|
||||
|
||||
## Parameters
|
||||
<PropsTable data={useStylesPropsDefs} isHook />
|
||||
|
||||
## Return Value
|
||||
|
||||
<PropsTable data={useStylesReturnDefs} isHook />
|
||||
|
||||
## How It Works
|
||||
|
||||
### Component Definition
|
||||
|
||||
A component definition describes how a component should be styled:
|
||||
|
||||
<CodeBlock code={useStylesDefsSnippet} />
|
||||
|
||||
<Banner variant="info" text="The data-attributes definition should be kebab-case instead of camelCase." />
|
||||
|
||||
### Data Attributes
|
||||
|
||||
Data attributes enable state-based styling through CSS selectors. The hook automatically adds `data-*` to the attributes based on the defined props:
|
||||
|
||||
<CodeBlock code={useStylesAttributesSnippet} />
|
||||
|
||||
|
||||
<ChangelogComponent component="use-styles" />
|
||||
@@ -0,0 +1,58 @@
|
||||
import { type PropDef } from '@/utils/propDefs';
|
||||
|
||||
export const useStylesPropsDefs: Record<string, PropDef> = {
|
||||
componentDefinition: {
|
||||
type: 'complex',
|
||||
complexType: {
|
||||
name: 'ComponentDefinition',
|
||||
properties: {
|
||||
classNames: {
|
||||
type: 'Record<string, string>',
|
||||
required: true,
|
||||
},
|
||||
dataAttributes: {
|
||||
type: 'Record<string, readonly (string | number | boolean)[]>',
|
||||
required: false,
|
||||
},
|
||||
utilityProps: {
|
||||
type: 'string[]',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
type: 'enum',
|
||||
description: 'All component props',
|
||||
values: ['{ [key: string]: any; }'],
|
||||
},
|
||||
};
|
||||
|
||||
export const useStylesReturnDefs: Record<string, PropDef> = {
|
||||
classNames: {
|
||||
type: 'enum',
|
||||
values: ['Record<string, string>'],
|
||||
description:
|
||||
"The component's class names mapped to their style definitions",
|
||||
},
|
||||
dataAttributes: {
|
||||
type: 'enum',
|
||||
values: ['Record<string, string>'],
|
||||
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<string, any>'],
|
||||
description: 'The original props with utility props removed',
|
||||
},
|
||||
};
|
||||
@@ -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<ButtonProps>) {
|
||||
const { classNames, dataAttributes, utilityClasses, cleanedProps } =
|
||||
useStyles(buttonDefinition, {
|
||||
variant,
|
||||
size,
|
||||
'is-disabled': isDisabled,
|
||||
...props,
|
||||
});
|
||||
|
||||
return (
|
||||
<button
|
||||
className={\`\${classNames.root} \${utilityClasses}\`}
|
||||
{...dataAttributes}
|
||||
{...cleanedProps}
|
||||
>
|
||||
{props.icon ?? <span className={classNames.icon}>{props.icon}</span>}
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
}`;
|
||||
|
||||
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;
|
||||
}`;
|
||||
Reference in New Issue
Block a user