diff --git a/.changeset/shaggy-cobras-rhyme.md b/.changeset/shaggy-cobras-rhyme.md new file mode 100644 index 0000000000..d8adefd112 --- /dev/null +++ b/.changeset/shaggy-cobras-rhyme.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +We are transforming how we structure our class names and data attributes definitions for all components. They are now all set in the same place. diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index 1b1e465a61..f7ac6dcb41 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -245,6 +245,9 @@ export interface CheckboxProps { value?: string; } +// @public +export type ClassNamesMap = Record; + // @public export const Collapsible: { Root: ForwardRefExoticComponent< @@ -267,6 +270,247 @@ export const Collapsible: { // @public (undocumented) export type Columns = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 'auto'; +// @public +export type ComponentClassNames = + (typeof componentDefinitions)[T]['classNames']; + +// @public +export interface ComponentDefinition { + // (undocumented) + classNames: ClassNamesMap; + // (undocumented) + dataAttributes?: DataAttributesMap; +} + +// @public +export type ComponentDefinitionName = keyof typeof componentDefinitions; + +// @public +export const componentDefinitions: { + readonly Avatar: { + readonly classNames: { + readonly root: 'canon-AvatarRoot'; + readonly image: 'canon-AvatarImage'; + readonly fallback: 'canon-AvatarFallback'; + }; + readonly dataAttributes: { + readonly size: readonly ['small', 'medium', 'large']; + }; + }; + readonly Box: { + readonly classNames: { + readonly root: 'canon-Box'; + }; + }; + readonly Button: { + readonly classNames: { + readonly root: 'canon-Button'; + }; + readonly dataAttributes: { + readonly size: readonly ['small', 'medium', 'large']; + readonly variant: readonly ['primary', 'secondary', 'ghost']; + }; + }; + readonly ButtonIcon: { + readonly classNames: { + readonly root: 'canon-ButtonIcon'; + }; + }; + readonly ButtonLink: { + readonly classNames: { + readonly root: 'canon-ButtonLink'; + }; + }; + readonly Checkbox: { + readonly classNames: { + readonly root: 'canon-CheckboxRoot'; + readonly label: 'canon-CheckboxLabel'; + readonly indicator: 'canon-CheckboxIndicator'; + }; + readonly dataAttributes: { + readonly checked: readonly [true, false]; + }; + }; + readonly Collapsible: { + readonly classNames: { + readonly root: 'canon-CollapsibleRoot'; + readonly trigger: 'canon-CollapsibleTrigger'; + readonly panel: 'canon-CollapsiblePanel'; + }; + }; + readonly Container: { + readonly classNames: { + readonly root: 'canon-Container'; + }; + }; + readonly FieldLabel: { + readonly classNames: { + readonly root: 'canon-FieldLabel'; + readonly label: 'canon-FieldLabelLabel'; + readonly secondaryLabel: 'canon-FieldLabelSecondaryLabel'; + readonly description: 'canon-FieldLabelDescription'; + }; + }; + readonly Flex: { + readonly classNames: { + readonly root: 'canon-Flex'; + }; + }; + readonly Grid: { + readonly classNames: { + readonly root: 'canon-Grid'; + readonly item: 'canon-GridItem'; + }; + }; + readonly Heading: { + readonly classNames: { + readonly root: 'canon-Heading'; + }; + readonly dataAttributes: { + readonly variant: readonly ['title1', 'title2', 'title3', 'subtitle']; + readonly color: readonly ['primary', 'secondary', 'muted']; + readonly truncate: readonly [true, false]; + }; + }; + readonly Icon: { + readonly classNames: { + readonly root: 'canon-Icon'; + }; + }; + readonly Link: { + readonly classNames: { + readonly root: 'canon-Link'; + }; + readonly dataAttributes: { + readonly variant: readonly ['subtitle', 'body', 'caption', 'label']; + readonly weight: readonly ['regular', 'bold']; + }; + }; + readonly Menu: { + readonly classNames: { + readonly trigger: 'canon-MenuTrigger'; + readonly backdrop: 'canon-MenuBackdrop'; + readonly positioner: 'canon-MenuPositioner'; + readonly popup: 'canon-MenuPopup'; + readonly arrow: 'canon-MenuArrow'; + readonly item: 'canon-MenuItem'; + readonly group: 'canon-MenuGroup'; + readonly groupLabel: 'canon-MenuGroupLabel'; + readonly radioGroup: 'canon-MenuRadioGroup'; + readonly radioItem: 'canon-MenuRadioItem'; + readonly radioItemIndicator: 'canon-MenuRadioItemIndicator'; + readonly checkboxItem: 'canon-MenuCheckboxItem'; + readonly checkboxItemIndicator: 'canon-MenuCheckboxItemIndicator'; + readonly submenuTrigger: 'canon-MenuSubmenuTrigger'; + readonly separator: 'canon-MenuSeparator'; + }; + }; + readonly RadioGroup: { + readonly classNames: { + readonly root: 'canon-RadioGroup'; + readonly content: 'canon-RadioGroupContent'; + readonly radio: 'canon-Radio'; + }; + }; + readonly ScrollArea: { + readonly classNames: { + readonly root: 'canon-ScrollAreaRoot'; + readonly viewport: 'canon-ScrollAreaViewport'; + readonly scrollbar: 'canon-ScrollAreaScrollbar'; + readonly thumb: 'canon-ScrollAreaThumb'; + }; + }; + readonly Select: { + readonly classNames: { + readonly root: 'canon-Select'; + readonly required: 'canon-SelectRequired'; + readonly trigger: 'canon-SelectTrigger'; + readonly value: 'canon-SelectValue'; + readonly icon: 'canon-SelectIcon'; + readonly popup: 'canon-SelectPopup'; + readonly item: 'canon-SelectItem'; + readonly itemIndicator: 'canon-SelectItemIndicator'; + readonly itemText: 'canon-SelectItemText'; + readonly description: 'canon-SelectDescription'; + readonly error: 'canon-SelectError'; + }; + readonly dataAttributes: { + readonly size: readonly ['small', 'medium']; + }; + }; + readonly Switch: { + readonly classNames: { + readonly root: 'canon-Switch'; + readonly indicator: 'canon-SwitchIndicator'; + }; + }; + readonly Table: { + readonly classNames: { + readonly root: 'canon-TableRoot'; + readonly header: 'canon-TableHeader'; + readonly body: 'canon-TableBody'; + readonly row: 'canon-TableRow'; + readonly head: 'canon-TableHead'; + readonly caption: 'canon-TableCaption'; + readonly cell: 'canon-TableCell'; + readonly cellText: 'canon-TableCellText'; + readonly cellLink: 'canon-TableCellLink'; + readonly cellProfile: 'canon-TableCellProfile'; + readonly cellProfileAvatar: 'canon-TableCellProfileAvatar'; + readonly cellProfileAvatarImage: 'canon-TableCellProfileAvatarImage'; + readonly cellProfileAvatarFallback: 'canon-TableCellProfileAvatarFallback'; + readonly cellProfileName: 'canon-TableCellProfileName'; + readonly cellProfileLink: 'canon-TableCellProfileLink'; + }; + }; + readonly Tabs: { + readonly classNames: { + readonly root: 'canon-TabsRoot'; + readonly list: 'canon-TabsList'; + readonly indicator: 'canon-TabsIndicator'; + readonly tab: 'canon-TabsTab'; + readonly panel: 'canon-TabsPanel'; + }; + }; + readonly Text: { + readonly classNames: { + readonly root: 'canon-Text'; + }; + readonly dataAttributes: { + readonly variant: readonly ['subtitle', 'body', 'caption', 'label']; + readonly weight: readonly ['regular', 'bold']; + readonly color: readonly [ + 'primary', + 'secondary', + 'danger', + 'warning', + 'success', + ]; + readonly truncate: readonly [true, false]; + }; + }; + readonly TextField: { + readonly classNames: { + readonly root: 'canon-TextField'; + readonly inputWrapper: 'canon-TextFieldInputWrapper'; + readonly icon: 'canon-TextFieldIcon'; + readonly input: 'canon-TextFieldInput'; + }; + readonly dataAttributes: { + readonly invalid: readonly [true, false]; + readonly disabled: readonly [true, false]; + }; + }; + readonly Tooltip: { + readonly classNames: { + readonly trigger: 'canon-TooltipTrigger'; + readonly positioner: 'canon-TooltipPositioner'; + readonly popup: 'canon-TooltipPopup'; + readonly arrow: 'canon-TooltipArrow'; + }; + }; +}; + // @public (undocumented) export const Container: ForwardRefExoticComponent< ContainerProps & RefAttributes @@ -294,6 +538,12 @@ export interface ContainerProps { style?: React.CSSProperties; } +// @public +export type DataAttributesMap = Record; + +// @public +export type DataAttributeValues = readonly (string | number | boolean)[]; + // @public export const DataTable: { Root: ( diff --git a/packages/canon/src/components/Avatar/Avatar.tsx b/packages/canon/src/components/Avatar/Avatar.tsx index 472bec39fb..4e899170de 100644 --- a/packages/canon/src/components/Avatar/Avatar.tsx +++ b/packages/canon/src/components/Avatar/Avatar.tsx @@ -18,28 +18,36 @@ import { forwardRef, ElementRef } from 'react'; import { Avatar as AvatarPrimitive } from '@base-ui-components/react/avatar'; import clsx from 'clsx'; import { AvatarProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const Avatar = forwardRef< ElementRef, AvatarProps ->(({ className, src, name, size = 'medium', ...props }, ref) => ( - - - - {(name || '') - .split(' ') - .map(word => word[0]) - .join('') - .toLocaleUpperCase('en-US') - .slice(0, 2)} - - -)); +>((props, ref) => { + const { className, src, name, size = 'medium', ...rest } = props; + const { classNames } = useStyles('Avatar', { + size, + }); + + return ( + + + + {(name || '') + .split(' ') + .map(word => word[0]) + .join('') + .toLocaleUpperCase('en-US') + .slice(0, 2)} + + + ); +}); Avatar.displayName = AvatarPrimitive.Root.displayName; diff --git a/packages/canon/src/components/Box/Box.tsx b/packages/canon/src/components/Box/Box.tsx index 8718c77724..4273ee86f9 100644 --- a/packages/canon/src/components/Box/Box.tsx +++ b/packages/canon/src/components/Box/Box.tsx @@ -24,6 +24,7 @@ import { widthPropDefs } from '../../props/width.props'; import { heightPropDefs } from '../../props/height.props'; import { positionPropDefs } from '../../props/position.props'; import { displayPropDefs } from '../../props/display.props'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const Box = forwardRef((props, ref) => { @@ -38,11 +39,12 @@ export const Box = forwardRef((props, ref) => { ...boxPropDefs, }; + const { classNames } = useStyles('Box'); const { className, style } = extractProps(props, propDefs); return createElement(props.as || 'div', { ref, - className: clsx('canon-Box', className), + className: clsx(classNames.root, className), style, children, }); diff --git a/packages/canon/src/components/Button/Button.tsx b/packages/canon/src/components/Button/Button.tsx index 9d256a5815..40879e51ed 100644 --- a/packages/canon/src/components/Button/Button.tsx +++ b/packages/canon/src/components/Button/Button.tsx @@ -17,8 +17,8 @@ import clsx from 'clsx'; import { forwardRef, Ref } from 'react'; import { Button as RAButton } from 'react-aria-components'; -import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import type { ButtonProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const Button = forwardRef( @@ -33,15 +33,16 @@ export const Button = forwardRef( ...rest } = props; - const responsiveSize = useResponsiveValue(size); - const responsiveVariant = useResponsiveValue(variant); + const { classNames, dataAttributes } = useStyles('Button', { + size, + variant, + }); return ( {iconStart} diff --git a/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx b/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx index 43c2f598ac..072ed20963 100644 --- a/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx +++ b/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx @@ -17,8 +17,8 @@ import clsx from 'clsx'; import { forwardRef, Ref } from 'react'; import { Button as RAButton } from 'react-aria-components'; -import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import type { ButtonIconProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const ButtonIcon = forwardRef( @@ -32,15 +32,18 @@ export const ButtonIcon = forwardRef( ...rest } = props; - const responsiveSize = useResponsiveValue(size); - const responsiveVariant = useResponsiveValue(variant); + const { classNames, dataAttributes } = useStyles('Button', { + size, + variant, + }); + + const { classNames: classNamesButtonIcon } = useStyles('ButtonIcon'); return ( {icon} diff --git a/packages/canon/src/components/ButtonLink/ButtonLink.tsx b/packages/canon/src/components/ButtonLink/ButtonLink.tsx index cb68401160..153643a530 100644 --- a/packages/canon/src/components/ButtonLink/ButtonLink.tsx +++ b/packages/canon/src/components/ButtonLink/ButtonLink.tsx @@ -17,8 +17,8 @@ import clsx from 'clsx'; import { forwardRef, Ref } from 'react'; import { Link as RALink } from 'react-aria-components'; -import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import type { ButtonLinkProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const ButtonLink = forwardRef( @@ -33,15 +33,18 @@ export const ButtonLink = forwardRef( ...rest } = props; - const responsiveSize = useResponsiveValue(size); - const responsiveVariant = useResponsiveValue(variant); + const { classNames, dataAttributes } = useStyles('Button', { + size, + variant, + }); + + const { classNames: classNamesButtonLink } = useStyles('ButtonLink'); return ( {iconStart} diff --git a/packages/canon/src/components/Checkbox/Checkbox.tsx b/packages/canon/src/components/Checkbox/Checkbox.tsx index 5bf6520891..aaa6fb105b 100644 --- a/packages/canon/src/components/Checkbox/Checkbox.tsx +++ b/packages/canon/src/components/Checkbox/Checkbox.tsx @@ -18,6 +18,7 @@ import { forwardRef } from 'react'; import { Checkbox as CheckboxPrimitive } from '@base-ui-components/react/checkbox'; import { Icon } from '@backstage/canon'; import type { CheckboxProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; import clsx from 'clsx'; /** @public */ @@ -35,10 +36,12 @@ export const Checkbox = forwardRef( style, } = props; + const { classNames } = useStyles('Checkbox'); + const checkboxElement = ( ( value={value} style={style} > - + ); return label ? ( -