diff --git a/docs-ui/src/content/components/box.mdx b/docs-ui/src/content/components/box.mdx
index 84fca48349..d1a9dd43fe 100644
--- a/docs-ui/src/content/components/box.mdx
+++ b/docs-ui/src/content/components/box.mdx
@@ -22,7 +22,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
}
+ preview={}
code={boxPreviewSnippet}
align="center"
/>
diff --git a/packages/ui/src/components/Box/Box.stories.tsx b/packages/ui/src/components/Box/Box.stories.tsx
index f0086c3d25..6637f45740 100644
--- a/packages/ui/src/components/Box/Box.stories.tsx
+++ b/packages/ui/src/components/Box/Box.stories.tsx
@@ -36,35 +36,277 @@ const meta = {
export default meta;
type Story = StoryObj;
-const Card = () => {
- return (
-
- );
-};
+const diagonalStripePattern = (() => {
+ const svg = `
+
+ `.trim();
+ return `data:image/svg+xml,${encodeURIComponent(svg)}`;
+})();
export const Default: Story = {
args: {
- children: 'Hello World',
- mb: '4',
+ width: '64px',
+ height: '64px',
+ style: {
+ background: '#eaf2fd',
+ borderRadius: '4px',
+ border: '1px solid #2563eb',
+ backgroundImage: `url("${diagonalStripePattern}")`,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ fontWeight: 'bold',
+ color: '#2563eb',
+ },
},
};
-export const Preview: Story = {
- args: {
- children: ,
- display: 'inline',
- },
+export const Margin: Story = {
+ args: { ...Default.args },
+ render: args => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
+
+export const ResponsiveMargin: Story = {
+ args: { ...Default.args },
+ render: args => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
+
+export const CustomMargin: Story = {
+ args: { ...Default.args },
+ render: args => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
+
+export const CustomResponsiveMargin: Story = {
+ args: { ...Default.args },
+ render: args => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
+
+export const Padding: Story = {
+ args: { ...Default.args },
+ render: args => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
+
+export const ResponsivePadding: Story = {
+ args: { ...Default.args },
+ render: args => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
+
+export const CustomPadding: Story = {
+ args: { ...Default.args },
+ render: args => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
+
+export const CustomResponsivePadding: Story = {
+ args: { ...Default.args },
+ render: args => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
};
const CardDisplay = ({ children }: { children?: ReactNode }) => {
@@ -105,151 +347,3 @@ export const Display: Story = {
),
};
-
-const styleInsideBox = {
- background: 'rgb(196, 202, 251)',
- color: 'white',
- borderRadius: '4px',
-};
-
-export const Padding: Story = {
- args: {
- style: {
- background: '#1f47ff',
- color: 'white',
- borderRadius: '4px',
- padding: '12px 12px',
- },
- },
- render: args => (
-
-
-
- Padding
-
-
- Padding X
-
-
- Padding Y
-
-
-
-
- Padding Top
-
-
- Padding Right
-
-
- Padding Bottom
-
-
- Padding Left
-
-
-
-
- Custom Padding
-
-
- Custom Padding X
-
-
- Custom Padding Y
-
-
-
-
- Custom Padding Top Bottom
-
-
- Custom Padding Left Right
-
-
-
- ),
-};
-
-export const Margin: Story = {
- args: {
- style: {
- background: '#1f47ff',
- color: 'white',
- borderRadius: '4px',
- padding: '12px 12px',
- },
- },
- render: args => (
-
-
-
-
- Margin
-
-
-
-
- Margin X
-
-
-
-
- Margin Y
-
-
-
-
-
-
- Margin Top
-
-
-
-
- Margin Right
-
-
-
-
- Margin Bottom
-
-
-
-
- Margin Left
-
-
-
-
-
-
- Custom Margin
-
-
-
-
- Custom Margin X
-
-
-
-
- Custom Margin Y
-
-
-
-
-
-
- Custom Margin Top Bottom
-
-
-
-
- Custom Margin Left Right
-
-
-
-
- ),
-};
diff --git a/packages/ui/src/components/Box/Box.tsx b/packages/ui/src/components/Box/Box.tsx
index 06d7d1d434..729a0f7cec 100644
--- a/packages/ui/src/components/Box/Box.tsx
+++ b/packages/ui/src/components/Box/Box.tsx
@@ -17,38 +17,27 @@
import { createElement, forwardRef } from 'react';
import { BoxProps } from './types';
import clsx from 'clsx';
-import { extractProps } from '../../utils/extractProps';
-import { spacingPropDefs } from '../../props/spacing.props';
-import { boxPropDefs } from './Box.props';
-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) => {
- const { children } = props;
+ const { classNames, utilityClasses, style, cleanedProps } = useStyles(
+ 'Box',
+ props,
+ );
- const propDefs = {
- ...spacingPropDefs,
- ...widthPropDefs,
- ...heightPropDefs,
- ...positionPropDefs,
- ...displayPropDefs,
- ...boxPropDefs,
- };
+ const { as = 'div', children, ...rest } = cleanedProps;
- const { classNames } = useStyles('Box');
- const { className, style, dataProps } = extractProps(props, propDefs);
-
- return createElement(props.as || 'div', {
- ref,
- className: clsx(classNames.root, className),
- ...dataProps,
- style,
+ return createElement(
+ as,
+ {
+ ref,
+ className: clsx(classNames.root, utilityClasses),
+ style,
+ ...rest,
+ },
children,
- });
+ );
});
Box.displayName = 'Box';
diff --git a/packages/ui/src/components/Box/types.ts b/packages/ui/src/components/Box/types.ts
index c70012dd56..c0f4a71035 100644
--- a/packages/ui/src/components/Box/types.ts
+++ b/packages/ui/src/components/Box/types.ts
@@ -14,24 +14,21 @@
* limitations under the License.
*/
-import type { HeightProps } from '../../props/height.props';
-import type { WidthProps } from '../../props/width.props';
-import type { PositionProps } from '../../props/position.props';
-import type { DisplayProps } from '../../props/display.props';
-import type { SpaceProps } from '../../types';
-import type { BoxOwnProps } from './Box.props';
+import type { SpaceProps, Responsive } from '../../types';
/** @public */
export interface BoxProps extends SpaceProps {
- display?: DisplayProps['display'];
- as?: BoxOwnProps['as'];
- width?: WidthProps['width'];
- minWidth?: WidthProps['minWidth'];
- maxWidth?: WidthProps['maxWidth'];
- height?: HeightProps['height'];
- minHeight?: HeightProps['minHeight'];
- maxHeight?: HeightProps['maxHeight'];
- position?: PositionProps['position'];
+ display?: Responsive<'none' | 'flex' | 'block' | 'inline'>;
+ as?: keyof JSX.IntrinsicElements;
+ width?: Responsive;
+ minWidth?: Responsive;
+ maxWidth?: Responsive;
+ height?: Responsive;
+ minHeight?: Responsive;
+ maxHeight?: Responsive;
+ position?: Responsive<
+ 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'
+ >;
children?: React.ReactNode;
className?: string;
style?: React.CSSProperties;
diff --git a/packages/ui/src/components/Button/Button.stories.tsx b/packages/ui/src/components/Button/Button.stories.tsx
index db8e999f98..9669b65d96 100644
--- a/packages/ui/src/components/Button/Button.stories.tsx
+++ b/packages/ui/src/components/Button/Button.stories.tsx
@@ -41,6 +41,7 @@ type Story = StoryObj;
export const Default: Story = {
args: {
children: 'Button',
+ className: 'caca',
},
};
diff --git a/packages/ui/src/components/Button/Button.tsx b/packages/ui/src/components/Button/Button.tsx
index 40879e51ed..610f29b9b0 100644
--- a/packages/ui/src/components/Button/Button.tsx
+++ b/packages/ui/src/components/Button/Button.tsx
@@ -23,21 +23,14 @@ import { useStyles } from '../../hooks/useStyles';
/** @public */
export const Button = forwardRef(
(props: ButtonProps, ref: Ref) => {
- const {
- size = 'small',
- variant = 'primary',
- iconStart,
- iconEnd,
- children,
- className,
- ...rest
- } = props;
-
- const { classNames, dataAttributes } = useStyles('Button', {
- size,
- variant,
+ const { classNames, dataAttributes, cleanedProps } = useStyles('Button', {
+ size: 'small',
+ variant: 'primary',
+ ...props,
});
+ const { children, className, iconStart, iconEnd, ...rest } = cleanedProps;
+
return (
} />,
};
-
-const variants = ['primary', 'secondary'] as const;
-const sizes = ['small', 'medium'] as const;
-
-export const Playground: Story = {
- render: args => (
-
- {variants.map(variant => (
-
- {variant}
- {sizes.map(size => (
-
- }
- />
- }
- aria-label="Chevron right icon button"
- variant={variant}
- size={size}
- />
- }
- aria-label="Chevron right icon button"
- variant={variant}
- size={size}
- />
-
- ))}
-
- ))}
-
- ),
-};
diff --git a/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx b/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx
index 072ed20963..19ed2de3f3 100644
--- a/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx
+++ b/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx
@@ -23,22 +23,16 @@ import { useStyles } from '../../hooks/useStyles';
/** @public */
export const ButtonIcon = forwardRef(
(props: ButtonIconProps, ref: Ref) => {
- const {
- size = 'small',
- variant = 'primary',
- icon,
- className,
- style,
- ...rest
- } = props;
-
- const { classNames, dataAttributes } = useStyles('Button', {
- size,
- variant,
+ const { classNames, dataAttributes, cleanedProps } = useStyles('Button', {
+ size: 'small',
+ variant: 'primary',
+ ...props,
});
const { classNames: classNamesButtonIcon } = useStyles('ButtonIcon');
+ const { className, icon, ...rest } = cleanedProps;
+
return (
(
-
- {variants.map(variant => (
-
- {variant}
- {sizes.map(size => (
-
-
- Button
-
- }
- variant={variant}
- size={size}
- >
- Button
-
- }
- variant={variant}
- size={size}
- >
- Button
-
- }
- iconEnd={}
- style={{ width: '200px' }}
- variant={variant}
- size={size}
- >
- Button
-
-
- Button
-
- }
- variant={variant}
- size={size}
- isDisabled
- >
- Button
-
- }
- variant={variant}
- size={size}
- isDisabled
- >
- Button
-
-
- ))}
-
- ))}
-
- ),
-};
diff --git a/packages/ui/src/components/ButtonLink/ButtonLink.tsx b/packages/ui/src/components/ButtonLink/ButtonLink.tsx
index b5b5ab208d..5c621bc057 100644
--- a/packages/ui/src/components/ButtonLink/ButtonLink.tsx
+++ b/packages/ui/src/components/ButtonLink/ButtonLink.tsx
@@ -26,24 +26,18 @@ import { isExternalLink } from '../../utils/isExternalLink';
export const ButtonLink = forwardRef(
(props: ButtonLinkProps, ref: Ref) => {
const navigate = useNavigate();
- const {
- size = 'small',
- variant = 'primary',
- iconStart,
- iconEnd,
- children,
- className,
- href,
- ...rest
- } = props;
- const { classNames, dataAttributes } = useStyles('Button', {
- size,
- variant,
+ const { classNames, dataAttributes, cleanedProps } = useStyles('Button', {
+ size: 'small',
+ variant: 'primary',
+ ...props,
});
const { classNames: classNamesButtonLink } = useStyles('ButtonLink');
+ const { children, className, iconStart, iconEnd, href, ...rest } =
+ cleanedProps;
+
const isExternal = isExternalLink(href);
// If it's an external link, render RALink without RouterProvider
diff --git a/packages/ui/src/css/utilities/mb.css b/packages/ui/src/css/utilities/mb.css
index 6394937af4..d2c3df3779 100644
--- a/packages/ui/src/css/utilities/mb.css
+++ b/packages/ui/src/css/utilities/mb.css
@@ -86,7 +86,7 @@
/* Breakpoint xs */
@media (min-width: 640px) {
.xs\:bui-mb {
- margin-bottom: var(--pb-xs);
+ margin-bottom: var(--mb-xs);
}
.xs\:bui-mb-0\.5 {
@@ -157,7 +157,7 @@
/* Breakpoint sm */
@media (min-width: 768px) {
.sm\:bui-mb {
- margin-bottom: var(--pb-sm);
+ margin-bottom: var(--mb-sm);
}
.sm\:bui-mb-0\.5 {
@@ -228,7 +228,7 @@
/* Breakpoint md */
@media (min-width: 1024px) {
.md\:bui-mb {
- margin-bottom: var(--pb-md);
+ margin-bottom: var(--mb-md);
}
.md\:bui-mb-0\.5 {
@@ -299,7 +299,7 @@
/* Breakpoint lg */
@media (min-width: 1280px) {
.lg\:bui-mb {
- margin-bottom: var(--pb-lg);
+ margin-bottom: var(--mb-lg);
}
.lg\:bui-mb-0\.5 {
@@ -370,7 +370,7 @@
/* Breakpoint xl */
@media (min-width: 1536px) {
.xl\:bui-mb {
- margin-bottom: var(--pb-xl);
+ margin-bottom: var(--mb-xl);
}
.xl\:bui-mb-0\.5 {
diff --git a/packages/ui/src/css/utilities/ml.css b/packages/ui/src/css/utilities/ml.css
index 7b82b73ab8..bfa17b1700 100644
--- a/packages/ui/src/css/utilities/ml.css
+++ b/packages/ui/src/css/utilities/ml.css
@@ -86,7 +86,7 @@
/* Breakpoint xs */
@media (min-width: 640px) {
.xs\:bui-ml {
- margin-left: var(--pl-xs);
+ margin-left: var(--ml-xs);
}
.xs\:bui-ml-0\.5 {
@@ -157,7 +157,7 @@
/* Breakpoint sm */
@media (min-width: 768px) {
.sm\:bui-ml {
- margin-left: var(--pl-sm);
+ margin-left: var(--ml-sm);
}
.sm\:bui-ml-0\.5 {
@@ -228,7 +228,7 @@
/* Breakpoint md */
@media (min-width: 1024px) {
.md\:bui-ml {
- margin-left: var(--pl-md);
+ margin-left: var(--ml-md);
}
.md\:bui-ml-0\.5 {
@@ -299,7 +299,7 @@
/* Breakpoint lg */
@media (min-width: 1280px) {
.lg\:bui-ml {
- margin-left: var(--pl-lg);
+ margin-left: var(--ml-lg);
}
.lg\:bui-ml-0\.5 {
@@ -370,7 +370,7 @@
/* Breakpoint xl */
@media (min-width: 1536px) {
.xl\:bui-ml {
- margin-left: var(--pl-xl);
+ margin-left: var(--ml-xl);
}
.xl\:bui-ml-0\.5 {
diff --git a/packages/ui/src/css/utilities/mr.css b/packages/ui/src/css/utilities/mr.css
index bac16b3ed3..96704994d8 100644
--- a/packages/ui/src/css/utilities/mr.css
+++ b/packages/ui/src/css/utilities/mr.css
@@ -86,7 +86,7 @@
/* Breakpoint xs */
@media (min-width: 640px) {
.xs\:bui-mr {
- margin-right: var(--pr-xs);
+ margin-right: var(--mr-xs);
}
.xs\:bui-mr-0\.5 {
@@ -157,7 +157,7 @@
/* Breakpoint sm */
@media (min-width: 768px) {
.sm\:bui-mr {
- margin-right: var(--pr-sm);
+ margin-right: var(--mr-sm);
}
.sm\:bui-mr-0\.5 {
@@ -228,7 +228,7 @@
/* Breakpoint md */
@media (min-width: 1024px) {
.md\:bui-mr {
- margin-right: var(--pr-md);
+ margin-right: var(--mr-md);
}
.md\:bui-mr-0\.5 {
@@ -299,7 +299,7 @@
/* Breakpoint lg */
@media (min-width: 1280px) {
.lg\:bui-mr {
- margin-right: var(--pr-lg);
+ margin-right: var(--mr-lg);
}
.lg\:bui-mr-0\.5 {
@@ -370,7 +370,7 @@
/* Breakpoint xl */
@media (min-width: 1536px) {
.xl\:bui-mr {
- margin-right: var(--pr-xl);
+ margin-right: var(--mr-xl);
}
.xl\:bui-mr-0\.5 {
diff --git a/packages/ui/src/css/utilities/mt.css b/packages/ui/src/css/utilities/mt.css
index 2f6ecc4aa8..d72cac5f93 100644
--- a/packages/ui/src/css/utilities/mt.css
+++ b/packages/ui/src/css/utilities/mt.css
@@ -86,7 +86,7 @@
/* Breakpoint xs */
@media (min-width: 640px) {
.xs\:bui-mt {
- margin-top: var(--pt-xs);
+ margin-top: var(--mt-xs);
}
.xs\:bui-mt-0\.5 {
@@ -157,7 +157,7 @@
/* Breakpoint sm */
@media (min-width: 768px) {
.sm\:bui-mt {
- margin-top: var(--pt-sm);
+ margin-top: var(--mt-sm);
}
.sm\:bui-mt-0\.5 {
@@ -228,7 +228,7 @@
/* Breakpoint md */
@media (min-width: 1024px) {
.md\:bui-mt {
- margin-top: var(--pt-md);
+ margin-top: var(--mt-md);
}
.md\:bui-mt-0\.5 {
@@ -299,7 +299,7 @@
/* Breakpoint lg */
@media (min-width: 1280px) {
.lg\:bui-mt {
- margin-top: var(--pt-lg);
+ margin-top: var(--mt-lg);
}
.lg\:bui-mt-0\.5 {
@@ -370,7 +370,7 @@
/* Breakpoint xl */
@media (min-width: 1536px) {
.xl\:bui-mt {
- margin-top: var(--pt-xl);
+ margin-top: var(--mt-xl);
}
.xl\:bui-mt-0\.5 {
diff --git a/packages/ui/src/css/utilities/mx.css b/packages/ui/src/css/utilities/mx.css
index 4898fc2352..f5c06b8b4c 100644
--- a/packages/ui/src/css/utilities/mx.css
+++ b/packages/ui/src/css/utilities/mx.css
@@ -103,8 +103,8 @@
/* Breakpoint xs */
@media (min-width: 640px) {
.xs\:bui-mx {
- margin-left: var(--px-xs);
- margin-right: var(--px-xs);
+ margin-left: var(--mx-xs);
+ margin-right: var(--mx-xs);
}
.xs\:bui-mx-0\.5 {
@@ -191,8 +191,8 @@
/* Breakpoint sm */
@media (min-width: 768px) {
.sm\:bui-mx {
- margin-left: var(--px-sm);
- margin-right: var(--px-sm);
+ margin-left: var(--mx-sm);
+ margin-right: var(--mx-sm);
}
.sm\:bui-mx-0\.5 {
@@ -279,8 +279,8 @@
/* Breakpoint md */
@media (min-width: 1024px) {
.md\:bui-mx {
- margin-left: var(--px-md);
- margin-right: var(--px-md);
+ margin-left: var(--mx-md);
+ margin-right: var(--mx-md);
}
.md\:bui-mx-0\.5 {
@@ -367,8 +367,8 @@
/* Breakpoint lg */
@media (min-width: 1280px) {
.lg\:bui-mx {
- margin-left: var(--px-lg);
- margin-right: var(--px-lg);
+ margin-left: var(--mx-lg);
+ margin-right: var(--mx-lg);
}
.lg\:bui-mx-0\.5 {
@@ -455,8 +455,8 @@
/* Breakpoint xl */
@media (min-width: 1536px) {
.xl\:bui-mx {
- margin-left: var(--px-xl);
- margin-right: var(--px-xl);
+ margin-left: var(--mx-xl);
+ margin-right: var(--mx-xl);
}
.xl\:bui-mx-0\.5 {
diff --git a/packages/ui/src/css/utilities/my.css b/packages/ui/src/css/utilities/my.css
index a165ad478e..f00167f88e 100644
--- a/packages/ui/src/css/utilities/my.css
+++ b/packages/ui/src/css/utilities/my.css
@@ -103,8 +103,8 @@
/* Breakpoint xs */
@media (min-width: 640px) {
.xs\:bui-my {
- margin-top: var(--py-xs);
- margin-bottom: var(--py-xs);
+ margin-top: var(--my-xs);
+ margin-bottom: var(--my-xs);
}
.xs\:bui-my-0\.5 {
@@ -191,8 +191,8 @@
/* Breakpoint sm */
@media (min-width: 768px) {
.sm\:bui-my {
- margin-top: var(--py-sm);
- margin-bottom: var(--py-sm);
+ margin-top: var(--my-sm);
+ margin-bottom: var(--my-sm);
}
.sm\:bui-my-0\.5 {
@@ -279,8 +279,8 @@
/* Breakpoint md */
@media (min-width: 1024px) {
.md\:bui-my {
- margin-top: var(--py-md);
- margin-bottom: var(--py-md);
+ margin-top: var(--my-md);
+ margin-bottom: var(--my-md);
}
.md\:bui-my-0\.5 {
@@ -367,8 +367,8 @@
/* Breakpoint lg */
@media (min-width: 1280px) {
.lg\:bui-my {
- margin-top: var(--py-lg);
- margin-bottom: var(--py-lg);
+ margin-top: var(--my-lg);
+ margin-bottom: var(--my-lg);
}
.lg\:bui-my-0\.5 {
@@ -455,8 +455,8 @@
/* Breakpoint xl */
@media (min-width: 1536px) {
.xl\:bui-my {
- margin-top: var(--py-xl);
- margin-bottom: var(--py-xl);
+ margin-top: var(--my-xl);
+ margin-bottom: var(--my-xl);
}
.xl\:bui-my-0\.5 {
diff --git a/packages/ui/src/hooks/useStyles.ts b/packages/ui/src/hooks/useStyles.ts
index 19f9823236..8e9259a1a3 100644
--- a/packages/ui/src/hooks/useStyles.ts
+++ b/packages/ui/src/hooks/useStyles.ts
@@ -17,6 +17,139 @@ import { useBreakpoint, breakpoints } from './useBreakpoint';
import { componentDefinitions } from '../utils/componentDefinitions';
import type { ComponentDefinitionName, ComponentClassNames } from '../types';
+// Valid spacing values that have predefined utility classes
+const VALID_SPACING_VALUES = [
+ '0.5',
+ '1',
+ '1.5',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ '10',
+ '11',
+ '12',
+ '13',
+ '14',
+] as const;
+
+const utilityClassMap = {
+ m: {
+ class: 'bui-m',
+ cssVar: '--m',
+ values: VALID_SPACING_VALUES,
+ },
+ mb: {
+ class: 'bui-mb',
+ cssVar: '--mb',
+ values: VALID_SPACING_VALUES,
+ },
+ ml: {
+ class: 'bui-ml',
+ cssVar: '--ml',
+ values: VALID_SPACING_VALUES,
+ },
+ mr: {
+ class: 'bui-mr',
+ cssVar: '--mr',
+ values: VALID_SPACING_VALUES,
+ },
+ mt: {
+ class: 'bui-mt',
+ cssVar: '--mt',
+ values: VALID_SPACING_VALUES,
+ },
+ mx: {
+ class: 'bui-mx',
+ cssVar: '--mx',
+ values: VALID_SPACING_VALUES,
+ },
+ my: {
+ class: 'bui-my',
+ cssVar: '--my',
+ values: VALID_SPACING_VALUES,
+ },
+ p: {
+ class: 'bui-p',
+ cssVar: '--p',
+ values: VALID_SPACING_VALUES,
+ },
+ pb: {
+ class: 'bui-pb',
+ cssVar: '--pb',
+ values: VALID_SPACING_VALUES,
+ },
+ pl: {
+ class: 'bui-pl',
+ cssVar: '--pl',
+ values: VALID_SPACING_VALUES,
+ },
+ pr: {
+ class: 'bui-pr',
+ cssVar: '--pr',
+ values: VALID_SPACING_VALUES,
+ },
+ pt: {
+ class: 'bui-pt',
+ cssVar: '--pt',
+ values: VALID_SPACING_VALUES,
+ },
+ px: {
+ class: 'bui-px',
+ cssVar: '--px',
+ values: VALID_SPACING_VALUES,
+ },
+ py: {
+ class: 'bui-py',
+ cssVar: '--py',
+ values: VALID_SPACING_VALUES,
+ },
+ width: {
+ class: 'bui-w',
+ cssVar: '--width',
+ values: VALID_SPACING_VALUES,
+ },
+ minWidth: {
+ class: 'bui-min-w',
+ cssVar: '--min-width',
+ values: VALID_SPACING_VALUES,
+ },
+ maxWidth: {
+ class: 'bui-max-w',
+ cssVar: '--max-width',
+ values: VALID_SPACING_VALUES,
+ },
+ height: {
+ class: 'bui-h',
+ cssVar: '--height',
+ values: VALID_SPACING_VALUES,
+ },
+ minHeight: {
+ class: 'bui-min-h',
+ cssVar: '--min-height',
+ values: VALID_SPACING_VALUES,
+ },
+ maxHeight: {
+ class: 'bui-max-h',
+ cssVar: '--max-height',
+ values: VALID_SPACING_VALUES,
+ },
+ position: {
+ class: 'bui-position',
+ cssVar: '--position',
+ values: ['static', 'relative', 'absolute', 'fixed', 'sticky'],
+ },
+ display: {
+ class: 'bui-display',
+ cssVar: '--display',
+ values: ['none', 'flex', 'block', 'inline'],
+ },
+};
+
/**
* Resolve a responsive value based on the current breakpoint
* @param value - The responsive value (string or object with breakpoint keys)
@@ -55,8 +188,8 @@ function resolveResponsiveValue(
/**
* React hook to get class names and data attributes for a component with responsive support
* @param componentName - The name of the component
- * @param props - Object with prop values (can be responsive)
- * @returns Object with classNames and dataAttributes
+ * @param props - All component props
+ * @returns Object with classNames, dataAttributes, utilityClasses, style, and cleanedProps
*/
export function useStyles(
componentName: T,
@@ -64,29 +197,125 @@ export function useStyles(
): {
classNames: ComponentClassNames;
dataAttributes: Record;
- resolvedProps: Record;
+ utilityClasses: string;
+ style: React.CSSProperties;
+ cleanedProps: Record;
} {
const { breakpoint } = useBreakpoint();
- const classNames = componentDefinitions[componentName]
- .classNames as ComponentClassNames;
+ const componentDefinition = componentDefinitions[componentName];
+ const classNames = componentDefinition.classNames as ComponentClassNames;
+ const utilityPropNames =
+ ('utilityProps' in componentDefinition
+ ? componentDefinition.utilityProps
+ : []) || [];
- // Resolve responsive values and generate data attributes
+ // Extract data attribute names from component definition
+ const dataAttributeNames =
+ 'dataAttributes' in componentDefinition
+ ? Object.keys(componentDefinition.dataAttributes || {})
+ : [];
+
+ // Extract existing style from props
+ const incomingStyle = props.style || {};
+
+ // Generate data attributes from component definition
const dataAttributes: Record = {};
- const resolvedProps: Record = {};
-
- for (const [key, value] of Object.entries(props)) {
+ for (const key of dataAttributeNames) {
+ const value = props[key];
if (value !== undefined && value !== null) {
const resolvedValue = resolveResponsiveValue(value, breakpoint);
if (resolvedValue !== undefined) {
- resolvedProps[key] = resolvedValue;
dataAttributes[`data-${key}`] = resolvedValue;
}
}
}
+ // Generate utility classes and custom styles from component's allowed utility props
+ const utilityClassList: string[] = [];
+ const generatedStyle: React.CSSProperties = {};
+
+ const handleUtilityValue = (
+ key: string,
+ val: unknown,
+ prefix: string = '',
+ ) => {
+ // Get utility class configuration for this key
+ const utilityConfig = utilityClassMap[key as keyof typeof utilityClassMap];
+
+ if (!utilityConfig) {
+ // Skip if no config found for this key
+ return;
+ }
+
+ // Check if value is in the list of valid values for this utility
+ if (utilityConfig.values.includes(val as any)) {
+ // Generate utility class with value suffix and optional breakpoint prefix
+ const className = prefix
+ ? `${prefix}${utilityConfig.class}-${val}`
+ : `${utilityConfig.class}-${val}`;
+ utilityClassList.push(className);
+ } else {
+ // Custom value - add CSS custom property AND utility class name
+ const cssVarKey = prefix
+ ? `${utilityConfig.cssVar}-${prefix.slice(0, -1)}`
+ : utilityConfig.cssVar;
+ // CSS custom properties need to be set as any since they're not part of CSSProperties
+ (generatedStyle as any)[cssVarKey] = val;
+
+ // Add utility class name (without value suffix) with optional breakpoint prefix
+ const className = prefix
+ ? `${prefix}${utilityConfig.class}`
+ : utilityConfig.class;
+ utilityClassList.push(className);
+ }
+ };
+
+ for (const key of utilityPropNames) {
+ const value = props[key];
+ if (value === undefined || value === null) {
+ continue;
+ }
+
+ // Check if value is a responsive object
+ if (typeof value === 'object' && value !== null) {
+ const breakpointValues = value as { [key: string]: unknown };
+ // Handle responsive object values
+ for (const bp in breakpointValues) {
+ const prefix = bp === 'initial' ? '' : `${bp}:`;
+ handleUtilityValue(key, breakpointValues[bp], prefix);
+ }
+ } else {
+ // Handle simple value
+ handleUtilityValue(key, value);
+ }
+ }
+
+ // Create cleaned props by excluding data attributes, utility props, and style
+ // Component-specific props like 'as' and 'children' remain in cleanedProps
+ const processedKeys = new Set([
+ ...dataAttributeNames,
+ ...utilityPropNames,
+ 'style',
+ ]);
+
+ const cleanedProps = Object.keys(props).reduce((acc, key) => {
+ if (!processedKeys.has(key)) {
+ acc[key] = props[key];
+ }
+ return acc;
+ }, {} as Record);
+
+ // Merge incoming style with generated styles (incoming styles take precedence)
+ const mergedStyle = {
+ ...generatedStyle,
+ ...incomingStyle,
+ };
+
return {
classNames,
dataAttributes,
- resolvedProps, // Also return resolved props for convenience
+ utilityClasses: utilityClassList.join(' '),
+ style: mergedStyle,
+ cleanedProps,
};
}
diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts
index d0011bfe8a..cde389113d 100644
--- a/packages/ui/src/types.ts
+++ b/packages/ui/src/types.ts
@@ -160,6 +160,7 @@ export type DataAttributesMap = Record;
export interface ComponentDefinition {
classNames: ClassNamesMap;
dataAttributes?: DataAttributesMap;
+ utilityProps?: string[];
}
/**
diff --git a/packages/ui/src/utils/componentDefinitions.ts b/packages/ui/src/utils/componentDefinitions.ts
index fe040d09b1..2516f85a84 100644
--- a/packages/ui/src/utils/componentDefinitions.ts
+++ b/packages/ui/src/utils/componentDefinitions.ts
@@ -35,6 +35,30 @@ export const componentDefinitions = {
classNames: {
root: 'bui-Box',
},
+ utilityProps: [
+ 'm',
+ 'mb',
+ 'ml',
+ 'mr',
+ 'mt',
+ 'mx',
+ 'my',
+ 'p',
+ 'pb',
+ 'pl',
+ 'pr',
+ 'pt',
+ 'px',
+ 'py',
+ 'position',
+ 'display',
+ 'width',
+ 'minWidth',
+ 'maxWidth',
+ 'height',
+ 'minHeight',
+ 'maxHeight',
+ ],
},
Button: {
classNames: {