diff --git a/.changeset/seven-cycles-pick.md b/.changeset/seven-cycles-pick.md new file mode 100644 index 0000000000..fbd1a3895f --- /dev/null +++ b/.changeset/seven-cycles-pick.md @@ -0,0 +1,17 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them. + +Affected components: + +- Menu, MenuListBox, MenuAutocomplete, MenuAutocompleteListbox, MenuItem, MenuListBoxItem, MenuSection, MenuSeparator +- Switch +- Skeleton +- FieldLabel +- Header, HeaderToolbar +- HeaderPage +- Tabs, TabList, Tab, TabPanel + +If you were passing custom className values to any of these components that relied on the previous behavior, you may need to adjust your styles to account for the default classes now being applied alongside your custom classes. diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index e0b8132f7f..4e762f431c 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -850,7 +850,8 @@ export const FieldLabel: ForwardRefExoticComponent< >; // @public (undocumented) -export interface FieldLabelProps { +export interface FieldLabelProps + extends Pick, 'className'> { description?: string | null; htmlFor?: string; id?: string; @@ -946,6 +947,8 @@ export interface HeaderPageProps { // (undocumented) breadcrumbs?: HeaderPageBreadcrumb[]; // (undocumented) + className?: string; + // (undocumented) customActions?: React.ReactNode; // (undocumented) tabs?: HeaderTab[]; @@ -955,6 +958,8 @@ export interface HeaderPageProps { // @public export interface HeaderProps { + // (undocumented) + className?: string; // (undocumented) customActions?: React.ReactNode; // (undocumented) @@ -1149,7 +1154,7 @@ export const RadioGroup: ForwardRefExoticComponent< // @public (undocumented) export interface RadioGroupProps extends Omit, - Omit { + Omit { // (undocumented) children?: ReactNode; } @@ -1171,7 +1176,7 @@ export const SearchField: ForwardRefExoticComponent< // @public (undocumented) export interface SearchFieldProps extends SearchFieldProps_2, - Omit { + Omit { icon?: ReactNode | false; placeholder?: string; size?: 'small' | 'medium' | Partial>; @@ -1189,7 +1194,7 @@ export interface SelectProps name: string; value: string; }>, - Omit { + Omit { icon?: ReactNode; options?: Array<{ value: string; @@ -1392,7 +1397,7 @@ export const TextField: ForwardRefExoticComponent< // @public (undocumented) export interface TextFieldProps extends TextFieldProps_2, - Omit { + Omit { icon?: ReactNode; placeholder?: string; size?: 'small' | 'medium' | Partial>; diff --git a/packages/ui/src/components/FieldLabel/FieldLabel.tsx b/packages/ui/src/components/FieldLabel/FieldLabel.tsx index 475b053344..5b27dd876f 100644 --- a/packages/ui/src/components/FieldLabel/FieldLabel.tsx +++ b/packages/ui/src/components/FieldLabel/FieldLabel.tsx @@ -24,14 +24,21 @@ import clsx from 'clsx'; export const FieldLabel = forwardRef( (props: FieldLabelProps, ref) => { const { classNames, cleanedProps } = useStyles('FieldLabel', props); - const { label, secondaryLabel, description, htmlFor, id, ...rest } = - cleanedProps; + const { + className, + label, + secondaryLabel, + description, + htmlFor, + id, + ...rest + } = cleanedProps; if (!label) return null; return (
diff --git a/packages/ui/src/components/FieldLabel/types.ts b/packages/ui/src/components/FieldLabel/types.ts index dc4e444133..7f1d8a24b1 100644 --- a/packages/ui/src/components/FieldLabel/types.ts +++ b/packages/ui/src/components/FieldLabel/types.ts @@ -15,7 +15,8 @@ */ /** @public */ -export interface FieldLabelProps { +export interface FieldLabelProps + extends Pick, 'className'> { /** * The label of the text field */ diff --git a/packages/ui/src/components/Header/Header.tsx b/packages/ui/src/components/Header/Header.tsx index ad5c704a3a..6f42820be4 100644 --- a/packages/ui/src/components/Header/Header.tsx +++ b/packages/ui/src/components/Header/Header.tsx @@ -35,8 +35,15 @@ declare module 'react-aria-components' { */ export const Header = (props: HeaderProps) => { const { classNames, cleanedProps } = useStyles('Header', props); - const { tabs, icon, title, titleLink, customActions, onTabSelectionChange } = - cleanedProps; + const { + className, + tabs, + icon, + title, + titleLink, + customActions, + onTabSelectionChange, + } = cleanedProps; const hasTabs = tabs && tabs.length > 0; @@ -54,6 +61,7 @@ export const Header = (props: HeaderProps) => { className={clsx( classNames.tabsWrapper, styles[classNames.tabsWrapper], + className, )} > diff --git a/packages/ui/src/components/Header/HeaderToolbar.tsx b/packages/ui/src/components/Header/HeaderToolbar.tsx index 343922ff02..a1754c61f4 100644 --- a/packages/ui/src/components/Header/HeaderToolbar.tsx +++ b/packages/ui/src/components/Header/HeaderToolbar.tsx @@ -31,7 +31,8 @@ import clsx from 'clsx'; */ export const HeaderToolbar = (props: HeaderToolbarProps) => { const { classNames, cleanedProps } = useStyles('Header', props); - const { icon, title, titleLink, customActions, hasTabs } = cleanedProps; + const { className, icon, title, titleLink, customActions, hasTabs } = + cleanedProps; let navigate = useNavigate(); // Refs for collision detection @@ -53,7 +54,11 @@ export const HeaderToolbar = (props: HeaderToolbarProps) => { return (
{ const { classNames, cleanedProps } = useStyles('HeaderPage', props); - const { title, tabs, customActions, breadcrumbs } = cleanedProps; + const { className, title, tabs, customActions, breadcrumbs } = cleanedProps; return ( - +
{ export const Menu = (props: MenuProps) => { const { classNames, cleanedProps } = useStyles('Menu', props); const { + className, placement = 'bottom start', virtualized = false, maxWidth, @@ -136,7 +137,11 @@ export const Menu = (props: MenuProps) => { return ( ) => { export const MenuListBox = (props: MenuListBoxProps) => { const { classNames, cleanedProps } = useStyles('Menu', props); const { + className, selectionMode = 'single', placement = 'bottom start', virtualized = false, @@ -184,7 +190,11 @@ export const MenuListBox = (props: MenuListBoxProps) => { return ( {virtualized ? ( @@ -207,6 +217,7 @@ export const MenuListBox = (props: MenuListBoxProps) => { export const MenuAutocomplete = (props: MenuAutocompleteProps) => { const { classNames, cleanedProps } = useStyles('Menu', props); const { + className, placement = 'bottom start', virtualized = false, maxWidth, @@ -229,7 +240,11 @@ export const MenuAutocomplete = (props: MenuAutocompleteProps) => { return ( @@ -281,6 +296,7 @@ export const MenuAutocompleteListbox = ( ) => { const { classNames, cleanedProps } = useStyles('Menu', props); const { + className, selectionMode = 'single', placement = 'bottom start', virtualized = false, @@ -304,7 +320,11 @@ export const MenuAutocompleteListbox = ( return ( @@ -352,6 +372,7 @@ export const MenuAutocompleteListbox = ( export const MenuItem = (props: MenuItemProps) => { const { classNames, cleanedProps } = useStyles('Menu', props); const { + className, iconStart, color = 'primary', children, @@ -365,7 +386,7 @@ export const MenuItem = (props: MenuItemProps) => { if (isLink && isExternal) { return ( window.open(href, '_blank', 'noopener,noreferrer')} @@ -398,7 +419,7 @@ export const MenuItem = (props: MenuItemProps) => { return ( { /** @public */ export const MenuListBoxItem = (props: MenuListBoxItemProps) => { const { classNames, cleanedProps } = useStyles('Menu', props); - const { children, ...rest } = cleanedProps; + const { children, className, ...rest } = cleanedProps; return (
{ /** @public */ export const MenuSection = (props: MenuSectionProps) => { const { classNames, cleanedProps } = useStyles('Menu', props); - const { children, title, ...rest } = cleanedProps; + const { children, className, title, ...rest } = cleanedProps; return ( ) => { /** @public */ export const MenuSeparator = (props: MenuSeparatorProps) => { const { classNames, cleanedProps } = useStyles('Menu', props); + const { className, ...rest } = cleanedProps; return ( ); }; diff --git a/packages/ui/src/components/PasswordField/types.ts b/packages/ui/src/components/PasswordField/types.ts index 8af9a6134c..6fade7496a 100644 --- a/packages/ui/src/components/PasswordField/types.ts +++ b/packages/ui/src/components/PasswordField/types.ts @@ -22,7 +22,7 @@ import type { FieldLabelProps } from '../FieldLabel/types'; /** @public */ export interface PasswordFieldProps extends AriaTextFieldProps, - Omit { + Omit { /** * An icon to render before the input */ diff --git a/packages/ui/src/components/RadioGroup/types.ts b/packages/ui/src/components/RadioGroup/types.ts index 029ef4baf9..8e2148401f 100644 --- a/packages/ui/src/components/RadioGroup/types.ts +++ b/packages/ui/src/components/RadioGroup/types.ts @@ -24,7 +24,7 @@ import { ReactNode } from 'react'; /** @public */ export interface RadioGroupProps extends Omit, - Omit { + Omit { children?: ReactNode; } diff --git a/packages/ui/src/components/SearchField/types.ts b/packages/ui/src/components/SearchField/types.ts index 5fb6552c2e..15bc6bd208 100644 --- a/packages/ui/src/components/SearchField/types.ts +++ b/packages/ui/src/components/SearchField/types.ts @@ -22,7 +22,7 @@ import type { FieldLabelProps } from '../FieldLabel/types'; /** @public */ export interface SearchFieldProps extends AriaSearchFieldProps, - Omit { + Omit { /** * An icon to render before the input */ diff --git a/packages/ui/src/components/Select/types.ts b/packages/ui/src/components/Select/types.ts index 6aea3452cc..6051a0e560 100644 --- a/packages/ui/src/components/Select/types.ts +++ b/packages/ui/src/components/Select/types.ts @@ -25,7 +25,7 @@ export interface SelectProps name: string; value: string; }>, - Omit { + Omit { /** * An icon to render before the input */ diff --git a/packages/ui/src/components/Skeleton/Skeleton.tsx b/packages/ui/src/components/Skeleton/Skeleton.tsx index d776ad3d3a..ca2f4ae493 100644 --- a/packages/ui/src/components/Skeleton/Skeleton.tsx +++ b/packages/ui/src/components/Skeleton/Skeleton.tsx @@ -27,11 +27,11 @@ export const Skeleton = (props: SkeletonProps) => { rounded: false, ...props, }); - const { width, height, rounded, style, ...rest } = cleanedProps; + const { className, width, height, rounded, style, ...rest } = cleanedProps; return (
( (props, ref) => { const { classNames, cleanedProps } = useStyles('Switch', props); - const { label, ...rest } = cleanedProps; + const { className, label, ...rest } = cleanedProps; return ( diff --git a/packages/ui/src/components/Tabs/Tabs.tsx b/packages/ui/src/components/Tabs/Tabs.tsx index d5db6fed21..f627199b4b 100644 --- a/packages/ui/src/components/Tabs/Tabs.tsx +++ b/packages/ui/src/components/Tabs/Tabs.tsx @@ -85,7 +85,7 @@ const isTabActive = ( */ export const Tabs = (props: TabsProps) => { const { classNames, cleanedProps } = useStyles('Tabs', props); - const { children, ...rest } = cleanedProps; + const { className, children, ...rest } = cleanedProps; const tabsRef = useRef(null); const tabRefs = useRef>(new Map()); const [hoveredKey, setHoveredKey] = useState(null); @@ -149,7 +149,7 @@ export const Tabs = (props: TabsProps) => { { */ export const TabList = (props: TabListProps) => { const { classNames, cleanedProps } = useStyles('Tabs', props); - const { children, ...rest } = cleanedProps; + const { className, children, ...rest } = cleanedProps; const { setHoveredKey, tabRefs, tabsRef, hoveredKey, prevHoveredKey } = useTabsContext(); @@ -193,6 +193,7 @@ export const TabList = (props: TabListProps) => { className={clsx( classNames.tabListWrapper, styles[classNames.tabListWrapper], + className, )} > { export const Tab = (props: TabProps) => { const { classNames, cleanedProps } = useStyles('Tabs', props); const { + className, href, children, id, @@ -231,7 +233,7 @@ export const Tab = (props: TabProps) => { return ( setTabRef(id as string, el as HTMLDivElement)} href={href} {...rest} @@ -248,11 +250,11 @@ export const Tab = (props: TabProps) => { */ export const TabPanel = (props: TabPanelProps) => { const { classNames, cleanedProps } = useStyles('Tabs', props); - const { children, ...rest } = cleanedProps; + const { className, children, ...rest } = cleanedProps; return ( {children} diff --git a/packages/ui/src/components/TextField/types.ts b/packages/ui/src/components/TextField/types.ts index b971ef8d26..456d6964a0 100644 --- a/packages/ui/src/components/TextField/types.ts +++ b/packages/ui/src/components/TextField/types.ts @@ -22,7 +22,7 @@ import type { FieldLabelProps } from '../FieldLabel/types'; /** @public */ export interface TextFieldProps extends AriaTextFieldProps, - Omit { + Omit { /** * The HTML input type for the text field *