Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Badge: make compact prop responsive #376

Merged
merged 2 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/great-onions-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@utilitywarehouse/web-ui': patch
---

Make the compact prop on `Badge` responsive
6 changes: 6 additions & 0 deletions packages/web-ui/src/Badge/Badge.docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ This prop will render a more compact Badge.
<Badge compact>Compact badge</Badge>
```

This prop is responsive, so you can set the value according to breakpoint values.

```tsx
<Badge compact={{ mobile: true, desktop: false }}>Responsive badge padding</Badge>
```

## Bottom radius

The `bottomRadiusZero` will remove the `border-bottom-right-radius` and `border-bottom-left-radius`, for use when the badge is positioned on top of another element.
Expand Down
3 changes: 2 additions & 1 deletion packages/web-ui/src/Badge/Badge.props.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ComponentPropsWithoutRef } from 'react';
import { Responsive } from '../types';

export interface BadgeProps extends ComponentPropsWithoutRef<'span'> {
/**
Expand Down Expand Up @@ -26,5 +27,5 @@ export interface BadgeProps extends ComponentPropsWithoutRef<'span'> {
* Sets a more compact padding
* @default false
*/
compact?: boolean;
compact?: Responsive<boolean>;
}
9 changes: 9 additions & 0 deletions packages/web-ui/src/Badge/Badge.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,12 @@ export const ContextualColour: Story = {
);
},
};

export const Compact: Story = {
name: 'Responsive padding',
render: args => (
<Badge {...args} compact={{ mobile: true, desktop: false }}>
Responsive badge padding
</Badge>
),
};
252 changes: 149 additions & 103 deletions packages/web-ui/src/Badge/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import {
DATA_ATTRIBUTES,
DATA_ATTRIBUTE_SELECTORS,
classSelector,
mediaQueries,
px,
pxToRem,
responsiveClassSelector,
translateBooleanValues,
withBreakpoints,
withGlobalPrefix,
} from '../utils';
import clsx from 'clsx';
Expand All @@ -20,121 +24,157 @@ const componentName = 'Badge';
const componentClassName = withGlobalPrefix(componentName);

const classNames = {
compact: withGlobalPrefix('compact'),
bottomRadiusZero: withGlobalPrefix('bottom-radius-zero'),
padding: {
compact: withGlobalPrefix('padding-compact'),
regular: withGlobalPrefix('padding-regular'),
},
variant: {
soft: withGlobalPrefix('variant-soft'),
strong: withGlobalPrefix('variant-strong'),
outline: withGlobalPrefix('variant-outline'),
},
bottomRadiusZero: withGlobalPrefix('bottom-radius-zero'),
};

const classSelectors = {
compact: classSelector(classNames.compact),
bottomRadiusZero: classSelector(classNames.bottomRadiusZero),
padding: {
compact: classSelector(classNames.padding.compact),
regular: classSelector(classNames.padding.regular),
tablet: {
compact: responsiveClassSelector(classNames.padding.compact, 'tablet'),
regular: responsiveClassSelector(classNames.padding.regular, 'tablet'),
},
desktop: {
compact: responsiveClassSelector(classNames.padding.compact, 'desktop'),
regular: responsiveClassSelector(classNames.padding.regular, 'desktop'),
},
wide: {
compact: responsiveClassSelector(classNames.padding.compact, 'wide'),
regular: responsiveClassSelector(classNames.padding.regular, 'wide'),
},
},
variant: {
soft: classSelector(classNames.variant.soft),
strong: classSelector(classNames.variant.strong),
outline: classSelector(classNames.variant.outline),
},
bottomRadiusZero: classSelector(classNames.bottomRadiusZero),
};

const StyledElement = styled('span')({
display: 'inline-flex',
gap: px(4),
alignItems: 'center',
whiteSpace: 'nowrap',
fontSize: pxToRem(14),
fontFamily: fonts.secondary,
fontWeight: fontWeights.secondary.regular,
fontStyle: 'normal',
flexShrink: 0,
lineHeight: pxToRem(16),
textAlign: 'center',
/* Make sure that the height is not stretched in a Flex/Grid layout */
height: 'fit-content',
'--badge-padding-inline': px(16),
'--badge-padding-inline-compact': px(8),
paddingBlock: px(4),
paddingInline: 'var(--badge-padding-inline)',
borderRadius: px(4),
color: 'var(--badge-color)',
backgroundColor: 'var(--badge-background-color)',
[classSelectors.compact]: {
paddingInline: 'var(--badge-padding-inline-compact)',
},
[classSelectors.bottomRadiusZero]: {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
},
[classSelectors.variant.soft]: {
'--badge-color': 'var(--badge-soft-color)',
'--badge-background-color': 'var(--badge-soft-background-color)',
},
[classSelectors.variant.strong]: {
'--badge-color': 'var(--badge-strong-color)',
'--badge-background-color': 'var(--badge-strong-background-color)',
},
[classSelectors.variant.outline]: {
'--badge-color': 'var(--badge-outline-color)',
'--badge-background-color': 'transparent',
'--badge-border-color': 'var(--badge-outline-border-color)',
boxShadow: 'inset 0 0 0 2px var(--badge-border-color)',
[DATA_ATTRIBUTE_SELECTORS.inverted]: {
'--badge-color': 'var(--badge-outline-color-inverted)',
'--badge-border-color': 'var(--badge-outline-border-color-inverted)',
const StyledElement = styled('span')(() => {
const paddingStyles = {
compact: { '--badge-padding-inline': 'var(--badge-padding-inline-compact)' },
regular: { '--badge-padding-inline': 'var(--badge-padding-inline-regular)' },
};

return {
display: 'inline-flex',
gap: px(4),
alignItems: 'center',
whiteSpace: 'nowrap',
fontSize: pxToRem(14),
fontFamily: fonts.secondary,
fontWeight: fontWeights.secondary.regular,
fontStyle: 'normal',
flexShrink: 0,
lineHeight: pxToRem(16),
textAlign: 'center',
height: 'fit-content' /* Make sure that the height is not stretched in a Flex/Grid layout */,
'--badge-padding-inline-regular': px(16),
'--badge-padding-inline-compact': px(8),
'--badge-padding-inline': 'var(--badge-padding-inline-regular)',
paddingBlock: px(4),
paddingInline: 'var(--badge-padding-inline)',
borderRadius: px(4),
color: 'var(--badge-color)',
backgroundColor: 'var(--badge-background-color)',
[classSelectors.padding.compact]: { ...paddingStyles.compact },
[classSelectors.padding.regular]: { ...paddingStyles.regular },
[mediaQueries.tablet]: {
[classSelectors.padding.tablet.compact]: { ...paddingStyles.compact },
[classSelectors.padding.tablet.regular]: { ...paddingStyles.regular },
},
},
[COLORSCHEME_SELECTORS.cyan]: {
'--badge-soft-color': colors.cyan900,
'--badge-soft-background-color': colors.cyan200,
'--badge-strong-color': colors.cyan50,
'--badge-strong-background-color': colors.cyan600,
'--badge-outline-color': colors.cyan900,
'--badge-outline-border-color': colors.cyan600,
'--badge-outline-color-inverted': colors.cyan50,
'--badge-outline-border-color-inverted': colors.cyan500,
},
[COLORSCHEME_SELECTORS.green]: {
'--badge-soft-color': colors.green900,
'--badge-soft-background-color': colors.green200,
'--badge-strong-color': colors.green50,
'--badge-strong-background-color': colors.green600,
'--badge-outline-color': colors.green900,
'--badge-outline-border-color': colors.green600,
'--badge-outline-color-inverted': colors.green50,
'--badge-outline-border-color-inverted': colors.green400,
},
[COLORSCHEME_SELECTORS.red]: {
'--badge-soft-color': colors.red900,
'--badge-soft-background-color': colors.red200,
'--badge-strong-color': colors.red50,
'--badge-strong-background-color': colors.red600,
'--badge-outline-color': colors.red900,
'--badge-outline-border-color': colors.red600,
'--badge-outline-color-inverted': colors.red50,
'--badge-outline-border-color-inverted': colors.red500,
},
[COLORSCHEME_SELECTORS.gold]: {
'--badge-soft-color': colors.gold900,
'--badge-soft-background-color': colors.gold200,
'--badge-strong-color': colors.gold900,
'--badge-strong-background-color': colors.gold300,
'--badge-outline-color': colors.gold900,
'--badge-outline-border-color': colors.gold500,
'--badge-outline-color-inverted': colors.gold50,
'--badge-outline-border-color-inverted': colors.gold400,
},
[COLORSCHEME_SELECTORS.grey]: {
'--badge-soft-color': colors.grey900,
'--badge-soft-background-color': colors.grey100,
'--badge-strong-color': colors.grey900,
'--badge-strong-background-color': colors.grey200,
'--badge-outline-color': colors.grey900,
'--badge-outline-border-color': colors.grey500,
'--badge-outline-color-inverted': colors.grey50,
'--badge-outline-border-color-inverted': colors.grey300,
},
[mediaQueries.desktop]: {
[classSelectors.padding.desktop.compact]: { ...paddingStyles.compact },
[classSelectors.padding.desktop.regular]: { ...paddingStyles.regular },
},
[mediaQueries.wide]: {
[classSelectors.padding.wide.compact]: { ...paddingStyles.compact },
[classSelectors.padding.wide.regular]: { ...paddingStyles.regular },
},
[classSelectors.bottomRadiusZero]: {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
},
[classSelectors.variant.soft]: {
'--badge-color': 'var(--badge-soft-color)',
'--badge-background-color': 'var(--badge-soft-background-color)',
},
[classSelectors.variant.strong]: {
'--badge-color': 'var(--badge-strong-color)',
'--badge-background-color': 'var(--badge-strong-background-color)',
},
[classSelectors.variant.outline]: {
'--badge-color': 'var(--badge-outline-color)',
'--badge-background-color': 'transparent',
'--badge-border-color': 'var(--badge-outline-border-color)',
boxShadow: 'inset 0 0 0 2px var(--badge-border-color)',
[DATA_ATTRIBUTE_SELECTORS.inverted]: {
'--badge-color': 'var(--badge-outline-color-inverted)',
'--badge-border-color': 'var(--badge-outline-border-color-inverted)',
},
},
[COLORSCHEME_SELECTORS.cyan]: {
'--badge-soft-color': colors.cyan900,
'--badge-soft-background-color': colors.cyan200,
'--badge-strong-color': colors.cyan50,
'--badge-strong-background-color': colors.cyan600,
'--badge-outline-color': colors.cyan900,
'--badge-outline-border-color': colors.cyan600,
'--badge-outline-color-inverted': colors.cyan50,
'--badge-outline-border-color-inverted': colors.cyan500,
},
[COLORSCHEME_SELECTORS.green]: {
'--badge-soft-color': colors.green900,
'--badge-soft-background-color': colors.green200,
'--badge-strong-color': colors.green50,
'--badge-strong-background-color': colors.green600,
'--badge-outline-color': colors.green900,
'--badge-outline-border-color': colors.green600,
'--badge-outline-color-inverted': colors.green50,
'--badge-outline-border-color-inverted': colors.green400,
},
[COLORSCHEME_SELECTORS.red]: {
'--badge-soft-color': colors.red900,
'--badge-soft-background-color': colors.red200,
'--badge-strong-color': colors.red50,
'--badge-strong-background-color': colors.red600,
'--badge-outline-color': colors.red900,
'--badge-outline-border-color': colors.red600,
'--badge-outline-color-inverted': colors.red50,
'--badge-outline-border-color-inverted': colors.red500,
},
[COLORSCHEME_SELECTORS.gold]: {
'--badge-soft-color': colors.gold900,
'--badge-soft-background-color': colors.gold200,
'--badge-strong-color': colors.gold900,
'--badge-strong-background-color': colors.gold300,
'--badge-outline-color': colors.gold900,
'--badge-outline-border-color': colors.gold500,
'--badge-outline-color-inverted': colors.gold50,
'--badge-outline-border-color-inverted': colors.gold400,
},
[COLORSCHEME_SELECTORS.grey]: {
'--badge-soft-color': colors.grey900,
'--badge-soft-background-color': colors.grey100,
'--badge-strong-color': colors.grey900,
'--badge-strong-background-color': colors.grey200,
'--badge-outline-color': colors.grey900,
'--badge-outline-border-color': colors.grey500,
'--badge-outline-color-inverted': colors.grey50,
'--badge-outline-border-color-inverted': colors.grey300,
},
};
});

/**
Expand Down Expand Up @@ -168,10 +208,16 @@ export const Badge = React.forwardRef<
return (
<StyledElement
ref={ref}
className={clsx(componentClassName, className, classNames.variant[variant], {
[classNames.compact]: compact,
[classNames.bottomRadiusZero]: bottomRadiusZero,
})}
className={clsx(
componentClassName,
className,
classNames.variant[variant],
withBreakpoints(
translateBooleanValues(compact, { true: 'compact', false: 'regular' }),
'padding'
),
{ [classNames.bottomRadiusZero]: bottomRadiusZero }
)}
{...dataAttributeProps}
{...props}
/>
Expand Down
33 changes: 30 additions & 3 deletions packages/web-ui/src/utils/with-breakpoints.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
import { Breakpoints, Responsive } from '../types';
import { withGlobalPrefix } from './utils';

/** Translates a responsive object of boolean values to a responsive object of string values. To be used with `withBreakpoints`. */
export const translateBooleanValues = (
value: Responsive<boolean>,
translation: { true: string; false: string }
) => {
if (typeof value === 'boolean') {
return translation[`${value}`];
}
if (typeof value === 'object') {
const updated = (Object.keys(value) as Array<Breakpoints>).reduce(
(acc: { [key: string]: string }, bp: Breakpoints) => {
const breakpointValue = value[bp];
if (breakpointValue !== undefined) {
acc[bp] = translation[`${breakpointValue}`];
}
return acc;
},
{}
);
return updated as Responsive<string>;
}
};

/** Creates a string of responsive classes */
export const withBreakpoints = (value: Responsive<string> | undefined, prefix = '') => {
if (typeof value === 'string') {
return withGlobalPrefix(`${prefix}-${value}`);
Expand All @@ -9,9 +33,12 @@ export const withBreakpoints = (value: Responsive<string> | undefined, prefix =
if (typeof value === 'object') {
const initialBreakpoint = 'mobile';
const classes = (Object.keys(value) as Array<Breakpoints>).map(bp => {
const baseClassName = withGlobalPrefix(`${prefix}-${value[bp]}`);
const className = bp === initialBreakpoint ? baseClassName : `${bp}:${baseClassName}`;
return className;
const breakpointValue = value[bp];
if (breakpointValue !== undefined) {
const baseClassName = withGlobalPrefix(`${prefix}-${breakpointValue}`);
const className = bp === initialBreakpoint ? baseClassName : `${bp}:${baseClassName}`;
return className;
}
});
return classes.join(' ');
}
Expand Down
Loading