Skip to content
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
11 changes: 0 additions & 11 deletions documentation-site/examples/badge/hint-dot.tsx

This file was deleted.

11 changes: 0 additions & 11 deletions documentation-site/examples/badge/notification-circle.tsx

This file was deleted.

24 changes: 1 addition & 23 deletions documentation-site/pages/components/badge.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import badgeYardConfig from "../../components/yard/config/badge";
import PrimaryInline from "examples/badge/primary-inline.tsx";
import SecondaryInline from "examples/badge/secondary-inline.tsx";
import Offset from "examples/badge/offset.tsx";
import NotificationCircleExample from "examples/badge/notification-circle.tsx";
import HintDotExample from "examples/badge/hint-dot.tsx";

export default Layout;

Expand All @@ -26,7 +24,7 @@ Badge content should generally be 3 words or less.

## Hierarchy

Primary badges are bright in color to grab a user's attention to an entry point of either a new product/feature, promotion or alert. Another usage is for transit lines. Avoid using multiple primary badges in the same view.
Primary badges are bright in color to grab a users attention to an entry point of either a new product/feature, promotion or alert. Another usage is for transit lines. Avoid using multiple primary badges in the same view.

Secondary badges should be part of the content inside the component. They are often meta data, highlighted information or simplified information.

Expand Down Expand Up @@ -55,24 +53,4 @@ There may be situations where it makes sense to deviate from the standard badge
- `horizontalOffset` sets the `right` CSS attribute when `placement` is `topRight` or `bottomRight`. Otherwise it sets the `left` attribute.
- `verticalOffset` sets the `top` CSS attribute when `placement` is `topLeft`, `top`, or `topRight`. Otherwise it sets the `bottom` attribute.

## NotificationCircle

Use `NotificationCircle` to display a count or icon indicator anchored to an element — for example, an unread message count on a navigation icon.

<Example title="Notification circle" path="badge/notification-circle.tsx">
<NotificationCircleExample />
</Example>

The `content` prop accepts a number, an icon element, or a render prop `(size: number) => ReactNode`. Numbers greater than 99 are automatically clamped to `99+`. Use the `size` prop (`small` or `medium`, defaults to `medium`) to control the circle dimensions. `NotificationCircle` supports `topLeft`, `topRight`, `bottomLeft`, and `bottomRight` placements.

## HintDot

Use `HintDot` to render a small colored dot anchored to an element, drawing attention without conveying specific information.

<Example title="Hint dot" path="badge/hint-dot.tsx">
<HintDotExample />
</Example>

The dot supports `topRight`, `topLeft`, `bottomRight`, and `bottomLeft` placements (defaults to `topRight`). By default a border separates the dot from its anchor; set `hasBorder={false}` to remove it. When no `children` are provided the dot renders inline without an anchor.

<Exports component={BadgeExports} title="Badge exports" path="baseui/badge" />
42 changes: 0 additions & 42 deletions src/badge/__tests__/utils.test.tsx

This file was deleted.

8 changes: 1 addition & 7 deletions src/badge/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,17 @@ export const HIERARCHY = Object.freeze({
secondary: 'secondary',
});

export const NOTIFICATION_CIRCLE_SIZE = {
small: 'small',
medium: 'medium',
} as const;

export const SHAPE = Object.freeze({
pill: 'pill',
rectangle: 'rectangle',
});

export const COLOR = Object.freeze({
accent: 'accent',
primary: 'primary', // deprecated
primary: 'primary',
positive: 'positive',
negative: 'negative',
warning: 'warning',
onBrand: 'onBrand',
});

export const PLACEMENT = Object.freeze({
Expand Down
16 changes: 5 additions & 11 deletions src/badge/hint-dot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import * as React from 'react';
import { useStyletron } from '../styles/index';
import { getOverrides } from '../helpers/overrides';
import { StyledHintDot, StyledRoot, StyledPositioner } from './styled-components';
import type { HintDotProps } from './types';
Expand All @@ -17,15 +18,14 @@ const HintDot = ({
horizontalOffset: horizontalOffsetProp,
verticalOffset: verticalOffsetProp,
hidden,
// placement was not there for hintBadge, but we need to support in for Avatar and other potential use cases.
placement = PLACEMENT.topRight,
hasBorder = true,
overrides = {},
}: HintDotProps) => {
const [HintDot, hintDotProps] = getOverrides(overrides.Badge, StyledHintDot);
const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot);
const [Positioner, positionerProps] = getOverrides(overrides.Positioner, StyledPositioner);

const [, theme] = useStyletron();

const anchor = getAnchorFromChildren(children);

// if the anchor is a string, we supply default offsets
Expand All @@ -39,28 +39,22 @@ const HintDot = ({
verticalOffset = '-4px';
}
}

return (
<Root {...rootProps}>
{anchor}

<Positioner
aria-hidden={true}
$horizontalOffset={horizontalOffset}
$verticalOffset={verticalOffset}
$placement={placement}
$placement={theme.direction === 'rtl' ? PLACEMENT.topLeft : PLACEMENT.topRight}
$role={ROLE.hintDot}
$noAnchor={!anchor}
$hasBorder={hasBorder}
{...positionerProps}
>
<HintDot
data-baseweb="hint-badge"
{...hintDotProps}
$color={color}
$horizontalOffset={horizontalOffset}
$hidden={hidden}
$hasBorder={hasBorder}
{...hintDotProps}
/>
</Positioner>
</Root>
Expand Down
2 changes: 1 addition & 1 deletion src/badge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export { default as Badge } from './badge';
export { default as NotificationCircle } from './notification-circle';
export { default as HintDot } from './hint-dot';

export { HIERARCHY, SHAPE, COLOR, PLACEMENT, NOTIFICATION_CIRCLE_SIZE } from './constants';
export { HIERARCHY, SHAPE, COLOR, PLACEMENT } from './constants';

export * from './styled-components';

Expand Down
49 changes: 7 additions & 42 deletions src/badge/notification-circle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as React from 'react';
import { getOverrides } from '../helpers/overrides';
import { StyledNotificationCircle, StyledRoot, StyledPositioner } from './styled-components';
import type { NotificationCircleProps } from './types';
import { PLACEMENT, ROLE, NOTIFICATION_CIRCLE_SIZE } from './constants';
import { PLACEMENT, ROLE } from './constants';
import { getAnchorFromChildren } from './utils';

const NotificationCircle = ({
Expand All @@ -19,7 +19,6 @@ const NotificationCircle = ({
horizontalOffset,
verticalOffset,
hidden,
size = NOTIFICATION_CIRCLE_SIZE.medium,
overrides = {},
}: NotificationCircleProps) => {
const [NotificationCircle, NotificationCircleProps] = getOverrides(
Expand All @@ -35,48 +34,22 @@ const NotificationCircle = ({
if (typeof contentProp === 'string') {
console.error(`[baseui] NotificationCircle child must be number or icon, found string`);
}
if (
placement &&
placement !== PLACEMENT.topLeft &&
placement !== PLACEMENT.topRight &&
placement !== PLACEMENT.bottomLeft &&
placement !== PLACEMENT.bottomRight
) {
if (placement && placement !== PLACEMENT.topLeft && placement !== PLACEMENT.topRight) {
console.error(
`[baseui] NotificationCircle must be placed topLeft, topRight, bottomLeft, or bottomRight, found ${placement}`
`[baseui] NotificationCircle must be placed topLeft or topRight, found ${placement}`
);
}
}

let content = contentProp;
const ICON_SIZE = size === NOTIFICATION_CIRCLE_SIZE.small ? 10 : 12;
const isContentNumber = typeof content === 'number';
if (typeof content === 'number' && content > 99) {
content = '99+';
} else if (typeof content === 'function') {
// add support for render prop, content = (size) => <Icon size={size} />
content = content(ICON_SIZE);
} else if (React.isValidElement(content)) {
// backwards compatibility for icon element as child, clone the element and pass size as prop
// content = <Icon />
// React.cloneElement is not recommended but we need this to support the old way of passing icon element as content
content = React.cloneElement(content as React.ReactElement<{ size?: number }>, {
size: ICON_SIZE,
});
if (typeof content === 'number' && content > 9) {
content = '9+';
}

// If there's no anchor, render the badge inline
if (!anchor) {
return (
<NotificationCircle
data-baseweb="notification-badge"
$color={color}
$hidden={hidden}
$size={size}
$extraPadding={isContentNumber}
aria-hidden={true}
{...NotificationCircleProps}
>
<NotificationCircle $color={color} $hidden={hidden} {...NotificationCircleProps}>
{content}
</NotificationCircle>
);
Expand All @@ -91,17 +64,9 @@ const NotificationCircle = ({
$verticalOffset={verticalOffset}
$placement={placement}
$role={ROLE.notificationCircle}
aria-hidden={true}
{...positionerProps}
>
<NotificationCircle
data-baseweb="notification-badge"
$color={color}
$hidden={hidden}
$size={size}
$extraPadding={isContentNumber}
{...NotificationCircleProps}
>
<NotificationCircle {...NotificationCircleProps} $color={color} $hidden={hidden}>
{content}
</NotificationCircle>
</Positioner>
Expand Down
Loading
Loading