@synerise/ds-badge 1.0.56 → 1.0.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.0.58](https://github.com/Synerise/synerise-design/compare/@synerise/ds-badge@1.0.57...@synerise/ds-badge@1.0.58) (2026-07-23)
7
+
8
+ **Note:** Version bump only for package @synerise/ds-badge
9
+
10
+ ## [1.0.57](https://github.com/Synerise/synerise-design/compare/@synerise/ds-badge@1.0.56...@synerise/ds-badge@1.0.57) (2026-06-17)
11
+
12
+ **Note:** Version bump only for package @synerise/ds-badge
13
+
6
14
  ## [1.0.56](https://github.com/Synerise/synerise-design/compare/@synerise/ds-badge@1.0.55...@synerise/ds-badge@1.0.56) (2026-06-11)
7
15
 
8
16
  **Note:** Version bump only for package @synerise/ds-badge
package/CLAUDE.md ADDED
@@ -0,0 +1,138 @@
1
+ # Badge (`@synerise/ds-badge`)
2
+
3
+ > DS-native badge (status dot, flag/pulsing dot, count badge). **antd-free** — exposes `ds-badge*`
4
+ > DOM/class hooks so downstream styled-overrides (e.g. ds-avatar) keep working. The legacy
5
+ > `ant-badge*` hooks have been dropped (so a parent app on the old antd-based badge can't leak its
6
+ > global `.ant-badge*` styles onto this one). Colour is driven by `status` (or `customColor`); there
7
+ > is no antd dependency.
8
+
9
+ ## Package structure
10
+
11
+ ```
12
+ src/
13
+ Badge.tsx — public entry: `text` → BadgeWithLabel, otherwise BadgeCore
14
+ BadgeCore.tsx — dot/count renderer (derives dot/count mode, renders the styled-components tree)
15
+ Badge.styles.tsx — styled-components (Wrapper, CountSup, ScrollNumberOnly, Current, DotSup, CustomCountSup) + colour helpers
16
+ Badge.types.ts — BadgeProps, Color, ColorHue, Status
17
+ BadgeWithLabel.tsx — status dot aligned next to a text label (powers the `text` prop; also exported)
18
+ BadgeWithLabel.styles.ts — inline-flex alignment for dot↔label
19
+ Badge.figma.tsx — Code Connect mappings (status-driven)
20
+ index.ts — public exports
21
+ ```
22
+
23
+ > No `style/` LESS folder — the antd badge LESS base was inlined into `Badge.styles.tsx`. Styling uses
24
+ > first-class styled-components with transient `$`-props; the elements carry `ds-badge-*` class names
25
+ > (for DS consumers / ui-tests / external CSS) but they are not used as styling selectors.
26
+
27
+ ## Public exports
28
+
29
+ ### `Badge` (default)
30
+
31
+ | Prop | Type | Default | Description |
32
+ |------|------|---------|-------------|
33
+ | `status` | `BadgeStatus` | `undefined` | DS status — drives the dot/count colour: `active`→green-600, `inactive`→grey-400, `blocked`→red-600, `processing`→blue-600, `warning`→yellow-600. Auto-enables dot mode. |
34
+ | `flag` | `boolean` | `undefined` | Renders the dot with `::before`/`::after` halos. Requires dot/status. |
35
+ | `pulsing` | `boolean` | `undefined` | Pulse animation on the halos. Only visible when `flag` or `status` is set. |
36
+ | `outlined` | `boolean` | `undefined` | White `box-shadow` ring around the count badge. |
37
+ | `customColor` | `LiteralStringUnion<BadgeColor \| DefaultColor>` | `undefined` | Overrides the badge colour. Palette token → resolved via `theme.palette` (`'blue'` → `blue-600`, or full key `'blue-600'`); any other value is used as a **raw CSS colour** (`'#FF0000'`, `'rgb(…)'`, …), so colours outside the palette work. |
38
+ | `count` | `ReactNode` | `undefined` | Number/node in the count badge. Hidden when `0`. |
39
+ | `dot` | `boolean` | `undefined` | Forces dot mode (auto-enabled by `status`). |
40
+ | `overflowCount` | `number` | `99` | Max number before `N+`. |
41
+ | `offset` | `[number\|string, number\|string]` | `undefined` | `[x, y]` offset of the badge relative to its children. |
42
+ | `children` | `ReactNode` | `undefined` | When omitted, the badge renders inline (standalone). |
43
+ | `text` | `ReactNode` | `undefined` | When set, renders a status/dot badge next to this label (delegates to `BadgeWithLabel`) — the legacy antd `status` + `text` API. Takes precedence over `count`/`children`; only `status`/`customColor`/`flag`/`pulsing`/`dot`/`className`/`style` apply in this mode. |
44
+ | `className`, `style` | — | — | Applied to the outer wrapper. |
45
+ | `data-*`, `aria-*` | — | — | Forwarded to the outermost wrapper (`PassthroughAttributes` from `@synerise/ds-utils`). |
46
+
47
+ ### `BadgeWithLabel` (named export)
48
+
49
+ A status/dot `Badge` aligned next to a text label. Owns the dot↔label alignment (inline-flex, 8px
50
+ gap) so consumers don't carry that CSS. It powers the `Badge` `text` prop and is also exported for
51
+ direct use.
52
+
53
+ | Prop | Type | Description |
54
+ |------|------|-------------|
55
+ | `status` / `customColor` / `flag` / `pulsing` / `dot` | — | Forwarded to the inner `Badge`. |
56
+ | `children` | `ReactNode` | The label rendered next to the dot. |
57
+ | `className`, `style` | — | Applied to the wrapper. |
58
+
59
+ ```tsx
60
+ import { BadgeWithLabel } from '@synerise/ds-badge';
61
+ <BadgeWithLabel status="warning">Needs review</BadgeWithLabel>
62
+ ```
63
+
64
+ ### Types
65
+
66
+ | Export | Description |
67
+ |--------|-------------|
68
+ | `BadgeProps` | Full props type (`BadgeOwnProps & PassthroughAttributes`) |
69
+ | `BadgeWithLabelProps` | Props for `BadgeWithLabel` |
70
+ | `BadgeStatus` | `'active' \| 'inactive' \| 'blocked' \| 'processing' \| 'warning' \| undefined` |
71
+ | `BadgeColor` | `'red' \| 'green' \| 'grey' \| 'yellow' \| 'blue' \| 'pink' \| 'mars' \| 'orange' \| 'fern' \| 'cyan' \| 'purple' \| 'violet' \| 'white' \| 'transparent'` |
72
+ | `BadgeColorHue` | `'900' \| '800' \| '700' \| '600' \| '500' \| '400' \| '300' \| '200' \| '100' \| '050'` |
73
+
74
+ ## Usage patterns
75
+
76
+ ```tsx
77
+ import Badge from '@synerise/ds-badge';
78
+
79
+ // Count badge (red by default; colour with status/customColor, or override via `style`)
80
+ <Badge count={5}><Avatar /></Badge>
81
+ <Badge count={3} status="blocked" />
82
+ <Badge count={3} customColor="blue" />
83
+
84
+ // Status dot (dot auto-enabled by status).
85
+ <Badge status="active" />
86
+
87
+ // Status dot + label — pass `text` (legacy API) or use BadgeWithLabel directly.
88
+ <Badge status="active" text="Online" />
89
+
90
+ // Flag dot with pulse
91
+ <Badge status="processing" flag pulsing><Avatar /></Badge>
92
+
93
+ // Custom dot colour (arbitrary, non-status colour)
94
+ <Badge status="active" customColor="purple-700" />
95
+
96
+ // data-* passthrough on the wrapper
97
+ <Badge status="active" data-testid="presence" />
98
+ ```
99
+
100
+ ## Styling
101
+
102
+ `Badge.styles.tsx` is `styled(BadgeBase)`. It **inlines the base badge CSS** that antd's LESS used to
103
+ provide (wrapper positioning, count pill, dot, `.ds-badge-not-a-wrapper` standalone layout) and adds
104
+ the DS overrides. DS-only styling props (`flag`, `pulsing`, `outlined`, `customColor`) are stripped in
105
+ the wrapper before reaching `BadgeBase`'s DOM; they are still read by the CSS interpolations.
106
+
107
+ Colours come from `@synerise/ds-core` theme palette tokens (`resolveColor`): `customColor` → `status`
108
+ colour → **red** default (count text is white). Consumers wanting a different look (e.g. a neutral
109
+ count chip) override it with an inline `style`/`styled(Badge)` — and, matching antd, the consumer's
110
+ `style` is applied to the **indicator element** (count/dot), not the wrapper, so
111
+ `style={{ backgroundColor: 'transparent', color: ... }}` recolours the count (e.g. the Tabs counter).
112
+ A custom-node `count` (e.g. an icon) renders bare via `.ds-badge-scroll-number-custom-component` — no pill
113
+ background. Count badge is 16px tall; count text uses `macro.h200` from `@synerise/ds-typography`. The
114
+ theme type is `ThemeProps` from `@synerise/ds-core` (not styled-components' `DefaultTheme`, unaugmented here).
115
+
116
+ ## Key dependencies
117
+
118
+ - `@synerise/ds-core` — theme palette tokens, `ThemeProps`, `DefaultColor`
119
+ - `@synerise/ds-typography` — `macro.h200` for count text
120
+ - `@synerise/ds-utils` — `PassthroughAttributes` (data-*/aria-* type)
121
+
122
+ ## Implementation notes
123
+
124
+ - **No antd.** `BadgeBase` renders the DOM with `ds-badge` / `ds-badge-count` / `ds-badge-dot` /
125
+ `ds-badge-status-dot` / `ds-badge-status-<status>` class hooks, which the DS consumers' selectors
126
+ target (e.g. ds-avatar's `& ~ .ds-badge-dot`). The dot `sup` carries `ds-badge-dot` **and**
127
+ `ds-badge-status-dot ds-badge-status-<status>` when `status` is set. The legacy `ant-badge-*` /
128
+ `ant-scroll-number-*` hooks were removed so a parent app still on the old antd-based badge can't
129
+ leak its global `.ant-badge*` styles onto the new one.
130
+ - **Colour is status-driven.** The old `backgroundColor` / `backgroundColorHue` / `textColor` /
131
+ `textColorHue` props and the antd-passthrough `color` / `text` props were **removed**; `showZero` /
132
+ `size` / `title` / `id` and the antd `Omit<AntBadgeProps>` inheritance are gone too. `customColor`
133
+ is kept for arbitrary per-item colours (segment lists, status-label table cells).
134
+ - **`dot` is auto-derived from `status`** — set `status` and you get a dot without `dot={true}`.
135
+ - **Standalone vs wrapping** — with no `children` the wrapper gets `ds-badge-not-a-wrapper` and the
136
+ badge renders inline (no absolute positioning / offset).
137
+ - **`text` restored** — when `text` is set, `Badge` delegates to `BadgeWithLabel` (status dot + label), so the legacy `status` + `text` API keeps working without switching components; `BadgeWithLabel` is still exported for direct use. To avoid a `Badge ↔ BadgeWithLabel` import cycle, the dot/count renderer lives in `BadgeCore.tsx` — both `Badge` (text mode) and `BadgeWithLabel` render `BadgeCore`.
138
+ - Tests: Vitest (`src/__specs__/Badge.spec.tsx`).
package/README.md CHANGED
@@ -42,7 +42,7 @@ import Badge from '@synerise/ds-badge'
42
42
  <Badge
43
43
  count={10}
44
44
  overflowCount={11}
45
- showZero={false}
45
+ status="processing"
46
46
  />
47
47
 
48
48
  ```
@@ -71,22 +71,28 @@ import Badge from '@synerise/ds-badge'
71
71
 
72
72
  | Property | Description | Type | Default |
73
73
  | ------------------ | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | ------- |
74
- | backgroundColor | Customize the badge color | Color | - |
75
- | backgroundColorHue | Customize brightness of color | `900` / `800` / `700` / `600` / `500` / `400` / `300` / `200` / `100` / `050` | - |
76
- | customColor | Customize the badge dot color | `BadgeColor` or `DefaultColor` (e.g. `'blue-600'`) | - |
77
- | count | Number to show in the badge counter | ReactNode | |
78
- | dot | Display a red dot instead of a counter | boolean | `false` |
79
- | flag | Show the badge as a flag | boolean | `false` |
80
- | offset | The offset of the badge dot in [x, y] format | [number, number] | - |
81
- | outlined | Add outline to the badge counter | boolean | `false` |
74
+ | status | Status that drives the dot/count colour | `active` / `inactive` / `blocked` / `processing` / `warning` | - |
75
+ | customColor | Override the badge colour palette token or any raw CSS colour | `BadgeColor` / `DefaultColor` / CSS colour (e.g. `'blue-600'`, `'#FF0000'`) | - |
76
+ | count | Number to show in the badge counter (hidden when `0`) | ReactNode | - |
77
+ | dot | Display a dot instead of a counter (auto-enabled by `status`) | boolean | `false` |
78
+ | flag | Show the badge as a flag (halo rings) | boolean | `false` |
79
+ | offset | The offset of the badge in [x, y] format | [number, number] | - |
80
+ | outlined | Add a white outline to the badge counter | boolean | `false` |
82
81
  | overflowCount | Maximum number to show in the counter | number | 99 |
83
82
  | pulsing | Enable pulsing animation | boolean | `false` |
84
- | showZero | Show the badge when the counter is zero | boolean | `false` |
85
- | status | Set badge as a status dot | `active` / `inactive` / `blocked` / `processing` / `warning` | - |
86
- | text | If `status` is set, `text` sets the display text of the status dot | string | '' |
87
- | textColor | Customize text color in badge | `red` / `green` / `grey` / `yellow` / `blue` / `pink`/ `mars`/ `orange`/ `fern`/ `cyan`/ `purple` / `violet` / `white` / `transparent` | - |
88
- | textColorHue | Customize brightness of color | `900` / `800` / `700` / `600` / `500` / `400` / `300` / `200` / `100` / `050` | - |
89
- | title | Text shown when a cursor is hovered over the badge | string | count |
83
+ | data-\* / aria-\* | Forwarded to the outermost wrapper element | string / number / boolean | - |
84
+
85
+ > **Removed in the antd-free version:** `backgroundColor`, `backgroundColorHue`, `textColor`, `textColorHue` (count colour is now driven by `status`), `text` (use the `BadgeWithLabel` export instead), `showZero`, `title`. The component no longer extends antd's Badge props.
86
+
87
+ ### `BadgeWithLabel`
88
+
89
+ A status dot aligned next to a text label — the replacement for the old `text` prop. Owns the dot↔label alignment so consumers don't.
90
+
91
+ ```
92
+ import { BadgeWithLabel } from '@synerise/ds-badge'
93
+
94
+ <BadgeWithLabel status="warning">Needs review</BadgeWithLabel>
95
+ ```
90
96
 
91
97
  ### Color
92
98
 
package/dist/Badge.d.ts CHANGED
@@ -1,4 +1,12 @@
1
1
  import { default as React } from 'react';
2
2
  import { BadgeProps } from './Badge.types';
3
- declare const Badge: ({ dot, ...props }: BadgeProps) => React.JSX.Element;
3
+ /**
4
+ * Public Badge.
5
+ *
6
+ * When `text` is provided the badge renders as a status/dot badge aligned next to that label —
7
+ * delegating to `BadgeWithLabel` (this restores the legacy antd `status` + `text` API so consumers
8
+ * don't have to switch to `BadgeWithLabel` by hand). Otherwise it renders the dot/count badge
9
+ * (`BadgeCore`).
10
+ */
11
+ declare const Badge: ({ text, ...props }: BadgeProps) => React.JSX.Element;
4
12
  export default Badge;
package/dist/Badge.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import StyledBadge from "./Badge.styles.js";
3
- import "./style/index.css";
2
+ import BadgeCore from "./BadgeCore.js";
3
+ import BadgeWithLabel from "./BadgeWithLabel.js";
4
4
  const Badge = ({
5
- dot,
5
+ text,
6
6
  ...props
7
7
  }) => {
8
- const {
9
- status
10
- } = props;
11
- const isDot = dot !== void 0 ? dot : status !== void 0;
12
- return /* @__PURE__ */ jsx(StyledBadge, { ...props, dot: isDot });
8
+ const hasText = text !== void 0 && text !== null && text !== false && text !== "";
9
+ if (hasText) {
10
+ return /* @__PURE__ */ jsx(BadgeWithLabel, { status: props.status, customColor: props.customColor, flag: props.flag, pulsing: props.pulsing, dot: props.dot, className: props.className, style: props.style, children: text });
11
+ }
12
+ return /* @__PURE__ */ jsx(BadgeCore, { ...props });
13
13
  };
14
14
  export {
15
15
  Badge as default
@@ -1,5 +1,22 @@
1
- import { default as React } from 'react';
1
+ import { Status } from './Badge.types';
2
+ type ColorProps = {
3
+ $status?: Status;
4
+ $customColor?: string;
5
+ };
6
+ type PositionProps = {
7
+ $standalone?: boolean;
8
+ };
2
9
  export declare const afterElementAnimation: import('styled-components').Keyframes;
3
10
  export declare const beforeElementAnimation: import('styled-components').Keyframes;
4
- declare const _default: import('styled-components').StyledComponent<({ flag, outlined, backgroundColor, textColor, backgroundColorHue, textColorHue, pulsing, customColor, ...rest }: any) => React.JSX.Element, any, {}, never>;
5
- export default _default;
11
+ export declare const Wrapper: import('styled-components').StyledComponent<"span", any, {}, never>;
12
+ export declare const CountSup: import('styled-components').StyledComponent<"sup", any, ColorProps & PositionProps & {
13
+ $outlined?: boolean;
14
+ }, never>;
15
+ export declare const ScrollNumberOnly: import('styled-components').StyledComponent<"span", any, {}, never>;
16
+ export declare const Current: import('styled-components').StyledComponent<"span", any, {}, never>;
17
+ export declare const CustomCountSup: import('styled-components').StyledComponent<"sup", any, PositionProps, never>;
18
+ export declare const DotSup: import('styled-components').StyledComponent<"sup", any, ColorProps & PositionProps & {
19
+ $flag?: boolean;
20
+ $pulsing?: boolean;
21
+ }, never>;
22
+ export {};
@@ -1,34 +1,64 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import { Badge } from "antd";
3
1
  import styled, { css, keyframes } from "styled-components";
4
2
  import { macro } from "@synerise/ds-typography";
5
- const getBackgroundColor = (props) => {
6
- if (props.backgroundColor === "transparent") {
7
- return "transparent";
3
+ const STATUS_COLOR_TOKEN = {
4
+ active: "green-600",
5
+ inactive: "grey-400",
6
+ blocked: "red-600",
7
+ processing: "blue-600",
8
+ warning: "yellow-600"
9
+ };
10
+ const resolveCustomColor = (theme, customColor) => {
11
+ const paletteColor = customColor.indexOf("-") >= 0 ? theme.palette[customColor] : theme.palette[`${customColor}-600`];
12
+ return paletteColor ?? customColor;
13
+ };
14
+ const resolveColor = (props) => {
15
+ const {
16
+ theme,
17
+ $customColor,
18
+ $status
19
+ } = props;
20
+ if ($customColor) {
21
+ return resolveCustomColor(theme, $customColor);
8
22
  }
9
- if (props.backgroundColor === "white") {
10
- return props.theme.palette.white;
23
+ if ($status) {
24
+ return theme.palette[STATUS_COLOR_TOKEN[$status]];
11
25
  }
12
- return props.theme.palette[`${props.backgroundColor}-${props.backgroundColorHue}`];
26
+ return theme.palette["red-600"];
13
27
  };
28
+ const indicatorPosition = (standalone) => standalone ? css(["position:relative;top:auto;right:auto;display:inline-block;vertical-align:middle;transform:none;"]) : css(["position:absolute;top:0;right:0;transform:translate(50%,-50%);transform-origin:100% 0%;"]);
14
29
  const afterElementAnimation = /* @__PURE__ */ keyframes(["0%{transform:translate3d(-5px,-5px,0) scale(0.3);opacity:0.9;}100%{transform:translate3d(-5px,-5px,0) scale(1.5);opacity:0;}"]);
15
30
  const beforeElementAnimation = /* @__PURE__ */ keyframes(["0%{transform:translate3d(-2px,-2px,0) scale(0.5);opacity:0.9;}100%{transform:translate3d(-2px,-2px,0) scale(1.5);opacity:0;}"]);
16
- const StyledBadge = styled(({
17
- flag,
18
- outlined,
19
- backgroundColor,
20
- textColor,
21
- backgroundColorHue,
22
- textColorHue,
23
- pulsing,
24
- customColor,
25
- ...rest
26
- }) => /* @__PURE__ */ jsx(Badge, { ...rest })).withConfig({
27
- displayName: "Badgestyles",
31
+ const Wrapper = /* @__PURE__ */ styled.span.withConfig({
32
+ displayName: "Badgestyles__Wrapper",
28
33
  componentId: "sc-1ci72bj-0"
29
- })(["&&{.ant-scroll-number-only{height:16px;&:last-of-type:not(:first-of-type) > p{padding-right:1px;}& > p{", ";color:inherit;line-height:16px;height:16px;font-weight:400;}}.ant-badge-count{box-shadow:none;height:16px;padding:0 3px;line-height:16px;min-width:16px;font-size:13px;background-color:", ";color:", ";}", ""], macro.h200, (props) => getBackgroundColor(props), (props) => props.theme.palette[`${props.textColor}-${props.textColorHue}`], (props) => css(["", " ", " ", " ", " ", " ", " ", ";"], props.customColor && css([".ant-badge-dot,.ant-badge-status-dot{background-color:", ";}"], props.customColor.indexOf("-") >= 0 ? props.theme.palette[props.customColor] : props.theme.palette[`${props.customColor}-600`]), props.status === "active" && !props.customColor && css([".ant-badge-status-active{background-color:", ";}"], props.theme.palette["green-600"]), props.status === "inactive" && !props.customColor && css([".ant-badge-status-inactive{background-color:", ";}"], props.theme.palette["grey-400"]), props.status === "blocked" && !props.customColor && css([".ant-badge-status-blocked{background-color:", ";}"], props.theme.palette["red-600"]), props.status === "processing" && !props.customColor && css([".ant-badge-status-processing{background-color:", ";}"], props.theme.palette["blue-600"]), props.outlined && css([".ant-badge-count{box-shadow:0 0 0 1px ", ";}"], props.theme.palette.white), (!!props.flag || !!props.status) && css([".ant-badge-dot{box-shadow:none;&.ant-badge-status-processing{display:inline-block;position:absolute;}}.ant-badge-dot,.ant-badge-status-dot{overflow:visible;border:2px solid ", ";width:10px;height:10px;&::before{display:flex;content:", ";transform:translate3d(-2px,-2px,0);", " transform-origin:center;}&::after{display:flex;content:", ";transform:translate3d(-5px,-5px,0);", " transform-origin:center;}}"], props.theme.palette.white, props.flag ? '""' : "none", props.pulsing && css(["animation:", " 2s infinite;position:absolute;top:0;left:0;width:10px;height:10px;background-color:inherit;border-radius:50%;"], beforeElementAnimation), props.flag ? '""' : "none", props.pulsing && css(["animation:", " 2s infinite;position:absolute;top:0;left:0;width:16px;height:16px;background-color:inherit;border-radius:50%;"], afterElementAnimation))));
34
+ })(["position:relative;display:inline-block;line-height:1;"]);
35
+ const CountSup = /* @__PURE__ */ styled.sup.withConfig({
36
+ displayName: "Badgestyles__CountSup",
37
+ componentId: "sc-1ci72bj-1"
38
+ })(["z-index:auto;", ";min-width:16px;height:16px;padding:0 3px;white-space:nowrap;text-align:center;border-radius:8px;background-color:", ";color:", ";box-shadow:", ";"], (props) => indicatorPosition(props.$standalone), (props) => resolveColor(props), (props) => props.theme.palette.white, (props) => props.$outlined ? `0 0 0 1px ${props.theme.palette.white}` : "none");
39
+ const ScrollNumberOnly = /* @__PURE__ */ styled.span.withConfig({
40
+ displayName: "Badgestyles__ScrollNumberOnly",
41
+ componentId: "sc-1ci72bj-2"
42
+ })(["display:inline-block;height:16px;"]);
43
+ const Current = /* @__PURE__ */ styled.span.withConfig({
44
+ displayName: "Badgestyles__Current",
45
+ componentId: "sc-1ci72bj-3"
46
+ })(["", ";height:16px;line-height:16px;font-weight:400;color:inherit;"], macro.h200);
47
+ const CustomCountSup = /* @__PURE__ */ styled.sup.withConfig({
48
+ displayName: "Badgestyles__CustomCountSup",
49
+ componentId: "sc-1ci72bj-4"
50
+ })(["z-index:auto;", ";"], (props) => indicatorPosition(props.$standalone));
51
+ const DotSup = /* @__PURE__ */ styled.sup.withConfig({
52
+ displayName: "Badgestyles__DotSup",
53
+ componentId: "sc-1ci72bj-5"
54
+ })(["z-index:auto;", ";width:6px;height:6px;border-radius:50%;background-color:", ";", ""], (props) => indicatorPosition(props.$standalone), (props) => resolveColor(props), (props) => (!!props.$flag || !!props.$status) && css(["box-sizing:border-box;width:10px;height:10px;overflow:visible;border:2px solid ", ";box-shadow:none;&::before{display:", ";content:", ";transform:translate3d(-2px,-2px,0);transform-origin:center;", "}&::after{display:", ";content:", ";transform:translate3d(-5px,-5px,0);transform-origin:center;", "}"], props.theme.palette.white, props.$flag ? "flex" : "none", props.$flag ? "''" : "none", props.$pulsing && css(["position:absolute;top:0;left:0;width:10px;height:10px;background-color:inherit;border-radius:50%;animation:", " 2s infinite;"], beforeElementAnimation), props.$flag ? "flex" : "none", props.$flag ? "''" : "none", props.$pulsing && css(["position:absolute;top:0;left:0;width:16px;height:16px;background-color:inherit;border-radius:50%;animation:", " 2s infinite;"], afterElementAnimation)));
30
55
  export {
56
+ CountSup,
57
+ Current,
58
+ CustomCountSup,
59
+ DotSup,
60
+ ScrollNumberOnly,
61
+ Wrapper,
31
62
  afterElementAnimation,
32
- beforeElementAnimation,
33
- StyledBadge as default
63
+ beforeElementAnimation
34
64
  };
@@ -1,17 +1,60 @@
1
- import { BadgeProps as AntBadgeProps } from 'antd';
1
+ import { CSSProperties, ReactNode } from 'react';
2
2
  import { DefaultColor } from '@synerise/ds-core';
3
+ import { LiteralStringUnion, PassthroughAttributes } from '@synerise/ds-utils';
3
4
  export declare const color: readonly ["red", "green", "grey", "yellow", "blue", "pink", "mars", "orange", "fern", "cyan", "purple", "violet", "white", "transparent"];
4
5
  export type Color = (typeof color)[number];
5
6
  export type ColorHue = '900' | '800' | '700' | '600' | '500' | '400' | '300' | '200' | '100' | '050';
6
7
  export type Status = 'active' | 'inactive' | 'blocked' | 'processing' | 'warning' | undefined;
7
- export type BadgeProps = Omit<AntBadgeProps, 'status'> & {
8
- flag?: boolean;
8
+ export type BadgeOwnProps = {
9
+ /**
10
+ * Content the badge is positioned over. When omitted, the badge renders inline (standalone).
11
+ */
12
+ children?: ReactNode;
13
+ /**
14
+ * When set, the badge renders as a status/dot badge aligned next to this label (delegating to
15
+ * `BadgeWithLabel`). Restores the legacy antd `status` + `text` API. Takes precedence over
16
+ * `count` / `children` — only `status`, `customColor`, `flag`, `pulsing`, `dot`, `className` and
17
+ * `style` apply in this mode.
18
+ */
19
+ text?: ReactNode;
20
+ /**
21
+ * DS status — drives the dot / count colour.
22
+ */
9
23
  status?: Status;
10
- outlined?: boolean;
11
- backgroundColor?: Color;
12
- textColor?: Color;
13
- backgroundColorHue?: ColorHue;
14
- textColorHue?: ColorHue;
24
+ /**
25
+ * Renders the dot with `::before`/`::after` halo rings. Requires dot/status to be active.
26
+ */
27
+ flag?: boolean;
28
+ /**
29
+ * Adds a repeating pulse animation to the halo rings. Only visible when `flag` or `status` is set.
30
+ */
15
31
  pulsing?: boolean;
16
- customColor?: Color | DefaultColor;
32
+ /**
33
+ * Adds a white outline ring around the count badge.
34
+ */
35
+ outlined?: boolean;
36
+ /**
37
+ * Overrides the badge colour. Accepts a palette token (`'blue'` → `blue-600`, or a full key like
38
+ * `'blue-600'`), or any raw CSS colour (`'#FF0000'`, `'rgb(…)'`, …) for colours outside the palette.
39
+ */
40
+ customColor?: LiteralStringUnion<Color | DefaultColor>;
41
+ /**
42
+ * Number (or node) shown inside the count badge.
43
+ */
44
+ count?: ReactNode;
45
+ /**
46
+ * Forces dot mode. Auto-enabled when `status` is provided.
47
+ */
48
+ dot?: boolean;
49
+ /**
50
+ * Maximum number before the count is shown as `N+`.
51
+ */
52
+ overflowCount?: number;
53
+ /**
54
+ * `[x, y]` pixel offset of the badge relative to its children.
55
+ */
56
+ offset?: [number | string, number | string];
57
+ className?: string;
58
+ style?: CSSProperties;
17
59
  };
60
+ export type BadgeProps = BadgeOwnProps & PassthroughAttributes;
@@ -0,0 +1,13 @@
1
+ import { default as React } from 'react';
2
+ import { BadgeProps } from './Badge.types';
3
+ /**
4
+ * DS-native, antd-free badge core (status dot / count). Renders DS styled-components (no styling via
5
+ * class selectors). Each element carries `ds-badge-*` class hooks for DS consumers (avatar, tabs,
6
+ * tag, …), external CSS and ui-tests. The legacy `ant-badge-*` / `ant-scroll-number-*` hooks have
7
+ * been dropped so a parent app still on the old antd-based badge can no longer leak its global
8
+ * `.ant-badge*` styles onto this one.
9
+ *
10
+ * Public entry is `./Badge` — it delegates here, or to `BadgeWithLabel` when `text` is set.
11
+ */
12
+ declare const BadgeCore: ({ children, status, count, dot, flag, pulsing, outlined, customColor, overflowCount, offset, className, style, ...passthrough }: BadgeProps) => React.JSX.Element;
13
+ export default BadgeCore;
@@ -0,0 +1,47 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { Wrapper, CustomCountSup, CountSup, ScrollNumberOnly, Current, DotSup } from "./Badge.styles.js";
4
+ const DEFAULT_OVERFLOW_COUNT = 99;
5
+ const toOffsetUnit = (value) => typeof value === "number" ? `${value}px` : value;
6
+ const renderCount = (count, overflowCount) => {
7
+ if (typeof count === "number" && count > overflowCount) {
8
+ return `${overflowCount}+`;
9
+ }
10
+ return count;
11
+ };
12
+ const BadgeCore = ({
13
+ children,
14
+ status,
15
+ count,
16
+ dot,
17
+ flag,
18
+ pulsing,
19
+ outlined,
20
+ customColor,
21
+ overflowCount = DEFAULT_OVERFLOW_COUNT,
22
+ offset,
23
+ className,
24
+ style,
25
+ ...passthrough
26
+ }) => {
27
+ const hasCount = count !== void 0 && count !== null && count !== false && count !== 0 && count !== "0";
28
+ const isDot = dot !== void 0 ? dot : status !== void 0 && !hasCount;
29
+ const hasChildren = children !== void 0 && children !== null && children !== false;
30
+ const standalone = !hasChildren;
31
+ const showCount = !isDot && hasCount;
32
+ const isCustomCount = React.isValidElement(count);
33
+ const wrapperClassName = ["ds-badge", standalone && "ds-badge-not-a-wrapper", className].filter(Boolean).join(" ");
34
+ const indicatorStyle = offset && hasChildren ? {
35
+ transform: `translate(calc(50% + ${toOffsetUnit(offset[0])}), calc(-50% + ${toOffsetUnit(offset[1])}))`,
36
+ ...style
37
+ } : style;
38
+ const statusClassName = status ? ` ds-badge-status-dot ds-badge-status-${status}` : "";
39
+ return /* @__PURE__ */ jsxs(Wrapper, { className: wrapperClassName, ...passthrough, children: [
40
+ children,
41
+ showCount && (isCustomCount ? /* @__PURE__ */ jsx(CustomCountSup, { className: "ds-badge-scroll-number-custom-component", $standalone: standalone, style: indicatorStyle, children: count }) : /* @__PURE__ */ jsx(CountSup, { className: "ds-badge-scroll-number ds-badge-count", $standalone: standalone, $status: status, $customColor: customColor, $outlined: outlined, title: typeof count === "string" || typeof count === "number" ? String(count) : void 0, style: indicatorStyle, children: /* @__PURE__ */ jsx(ScrollNumberOnly, { className: "ds-badge-scroll-number-only", children: /* @__PURE__ */ jsx(Current, { className: "current", children: renderCount(count, overflowCount) }) }) })),
42
+ isDot && /* @__PURE__ */ jsx(DotSup, { className: `ds-badge-dot${statusClassName}`, $standalone: standalone, $status: status, $customColor: customColor, $flag: flag, $pulsing: pulsing, style: indicatorStyle })
43
+ ] });
44
+ };
45
+ export {
46
+ BadgeCore as default
47
+ };
@@ -0,0 +1,14 @@
1
+ import { default as React, CSSProperties, ReactNode } from 'react';
2
+ import { BadgeProps } from './Badge.types';
3
+ export type BadgeWithLabelProps = Pick<BadgeProps, 'status' | 'customColor' | 'flag' | 'pulsing' | 'dot'> & {
4
+ /** Label rendered next to the badge dot. */
5
+ children?: ReactNode;
6
+ className?: string;
7
+ style?: CSSProperties;
8
+ };
9
+ /**
10
+ * A status/dot `Badge` aligned next to a text label — the antd-free replacement for the old
11
+ * `Badge` `text` prop. Owns the dot↔label alignment so consumers don't have to.
12
+ */
13
+ declare const BadgeWithLabel: ({ children, className, style, ...badgeProps }: BadgeWithLabelProps) => React.JSX.Element;
14
+ export default BadgeWithLabel;
@@ -0,0 +1,15 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import BadgeCore from "./BadgeCore.js";
3
+ import { Wrapper, Label } from "./BadgeWithLabel.styles.js";
4
+ const BadgeWithLabel = ({
5
+ children,
6
+ className,
7
+ style,
8
+ ...badgeProps
9
+ }) => /* @__PURE__ */ jsxs(Wrapper, { className, style, children: [
10
+ /* @__PURE__ */ jsx(BadgeCore, { ...badgeProps }),
11
+ /* @__PURE__ */ jsx(Label, { children })
12
+ ] });
13
+ export {
14
+ BadgeWithLabel as default
15
+ };
@@ -0,0 +1,2 @@
1
+ export declare const Wrapper: import('styled-components').StyledComponent<"span", any, {}, never>;
2
+ export declare const Label: import('styled-components').StyledComponent<"span", any, {}, never>;
@@ -0,0 +1,13 @@
1
+ import styled from "styled-components";
2
+ const Wrapper = /* @__PURE__ */ styled.span.withConfig({
3
+ displayName: "BadgeWithLabelstyles__Wrapper",
4
+ componentId: "sc-14o8m20-0"
5
+ })(["display:inline-flex;align-items:center;gap:8px;"]);
6
+ const Label = /* @__PURE__ */ styled.span.withConfig({
7
+ displayName: "BadgeWithLabelstyles__Label",
8
+ componentId: "sc-14o8m20-1"
9
+ })(["line-height:1;color:", ";"], (props) => props.theme.palette["grey-600"]);
10
+ export {
11
+ Label,
12
+ Wrapper
13
+ };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { default } from './Badge';
2
+ export { default as BadgeWithLabel, type BadgeWithLabelProps, } from './BadgeWithLabel';
2
3
  export type { Status as BadgeStatus, BadgeProps, Color as BadgeColor, ColorHue as BadgeColorHue, } from './Badge.types';
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { default as default2 } from "./Badge.js";
2
+ import { default as default3 } from "./BadgeWithLabel.js";
2
3
  export {
4
+ default3 as BadgeWithLabel,
3
5
  default2 as default
4
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synerise/ds-badge",
3
- "version": "1.0.56",
3
+ "version": "1.0.58",
4
4
  "description": "Badge UI Component for the Synerise Design System",
5
5
  "license": "ISC",
6
6
  "repository": "Synerise/synerise-design",
@@ -17,6 +17,7 @@
17
17
  "files": [
18
18
  "/dist",
19
19
  "CHANGELOG.md",
20
+ "CLAUDE.md",
20
21
  "README.md",
21
22
  "package.json",
22
23
  "LICENSE.md"
@@ -41,15 +42,15 @@
41
42
  ],
42
43
  "types": "dist/index.d.ts",
43
44
  "dependencies": {
44
- "@synerise/ds-icon": "^1.18.3",
45
- "@synerise/ds-typography": "^1.1.25"
45
+ "@synerise/ds-icon": "^1.18.5",
46
+ "@synerise/ds-typography": "^1.1.27",
47
+ "@synerise/ds-utils": "^1.10.2"
46
48
  },
47
49
  "peerDependencies": {
48
50
  "@synerise/ds-core": "*",
49
- "antd": "4.24.16",
50
51
  "react": ">=16.9.0 <= 18.3.1",
51
52
  "styled-components": "^5.3.3",
52
53
  "vitest": "4"
53
54
  },
54
- "gitHead": "fe3379f50afdce6d8c61a2222ebbf03324107c95"
55
+ "gitHead": "d0a43cc43d8528a36f105aceea52ab470edb71d9"
55
56
  }
File without changes
@@ -1 +0,0 @@
1
- .ant-badge{box-sizing:border-box;margin:0;padding:0;color:#6a7580;font-size:13px;font-variant:tabular-nums;line-height:1.38;list-style:none;font-feature-settings:'tnum';position:relative;display:inline-block;line-height:1}.ant-badge-count{z-index:auto;min-width:20px;height:20px;padding:0 6px;color:#fff;font-weight:400;font-size:11px;line-height:20px;white-space:nowrap;text-align:center;background:#ff4d4f;border-radius:10px;box-shadow:0 0 0 1px #fff}.ant-badge-count a,.ant-badge-count a:hover{color:#fff}.ant-badge-count-sm{min-width:14px;height:14px;padding:0;font-size:11px;line-height:14px;border-radius:7px}.ant-badge-multiple-words{padding:0 8px}.ant-badge-dot{z-index:auto;width:6px;min-width:6px;height:6px;background:#ff4d4f;border-radius:100%;box-shadow:0 0 0 1px #fff}.ant-badge-dot.ant-scroll-number{transition:background 1.5s}.ant-badge .ant-scroll-number-custom-component,.ant-badge-count,.ant-badge-dot{position:absolute;top:0;right:0;transform:translate(50%,-50%);transform-origin:100% 0%}.ant-badge .ant-scroll-number-custom-component.anticon-spin,.ant-badge-count.anticon-spin,.ant-badge-dot.anticon-spin{animation:antBadgeLoadingCircle 1s infinite linear}.ant-badge-status{line-height:inherit;vertical-align:baseline}.ant-badge-status-dot{position:relative;top:-1px;display:inline-block;width:6px;height:6px;vertical-align:middle;border-radius:50%}.ant-badge-status-success{background-color:#54cb0b}.ant-badge-status-processing{position:relative;background-color:#1890ff}.ant-badge-status-processing::after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:50%;animation:antStatusProcessing 1.2s infinite ease-in-out;content:''}.ant-badge-status-default{background-color:#d9d9d9}.ant-badge-status-error{background-color:#f52922}.ant-badge-status-warning{background-color:#fab700}.ant-badge-status-pink{background:#eb2f96}.ant-badge-status-magenta{background:#eb2f96}.ant-badge-status-red{background:#f5222d}.ant-badge-status-volcano{background:#fa541c}.ant-badge-status-orange{background:#fa8c16}.ant-badge-status-yellow{background:#fadb14}.ant-badge-status-gold{background:#faad14}.ant-badge-status-cyan{background:#13c2c2}.ant-badge-status-lime{background:#a0d911}.ant-badge-status-green{background:#52c41a}.ant-badge-status-blue{background:#1890ff}.ant-badge-status-geekblue{background:#2f54eb}.ant-badge-status-purple{background:#722ed1}.ant-badge-status-text{margin-left:8px;color:#6a7580;font-size:13px}.ant-badge-zoom-appear,.ant-badge-zoom-enter{animation:antZoomBadgeIn .3s cubic-bezier(.12,.4,.29,1.46);animation-fill-mode:both}.ant-badge-zoom-leave{animation:antZoomBadgeOut .3s cubic-bezier(.71,-.46,.88,.6);animation-fill-mode:both}.ant-badge-not-a-wrapper .ant-badge-zoom-appear,.ant-badge-not-a-wrapper .ant-badge-zoom-enter{animation:antNoWrapperZoomBadgeIn .3s cubic-bezier(.12,.4,.29,1.46)}.ant-badge-not-a-wrapper .ant-badge-zoom-leave{animation:antNoWrapperZoomBadgeOut .3s cubic-bezier(.71,-.46,.88,.6)}.ant-badge-not-a-wrapper:not(.ant-badge-status){vertical-align:middle}.ant-badge-not-a-wrapper .ant-badge-count,.ant-badge-not-a-wrapper .ant-scroll-number-custom-component{transform:none}.ant-badge-not-a-wrapper .ant-scroll-number,.ant-badge-not-a-wrapper .ant-scroll-number-custom-component{position:relative;top:auto;display:block;transform-origin:50% 50%}@keyframes antStatusProcessing{0%{transform:scale(.8);opacity:.5}100%{transform:scale(2.4);opacity:0}}.ant-scroll-number{overflow:hidden;direction:ltr}.ant-scroll-number-only{position:relative;display:inline-block;height:20px;transition:all .3s cubic-bezier(.645, .045, .355, 1);-webkit-transform-style:preserve-3d;-webkit-backface-visibility:hidden}.ant-scroll-number-only>p.ant-scroll-number-only-unit{height:20px;margin:0;-webkit-transform-style:preserve-3d;-webkit-backface-visibility:hidden}.ant-scroll-number-symbol{vertical-align:top}@keyframes antZoomBadgeIn{0%{transform:scale(0) translate(50%,-50%);opacity:0}100%{transform:scale(1) translate(50%,-50%)}}@keyframes antZoomBadgeOut{0%{transform:scale(1) translate(50%,-50%)}100%{transform:scale(0) translate(50%,-50%);opacity:0}}@keyframes antNoWrapperZoomBadgeIn{0%{transform:scale(0);opacity:0}100%{transform:scale(1)}}@keyframes antNoWrapperZoomBadgeOut{0%{transform:scale(1)}100%{transform:scale(0);opacity:0}}@keyframes antBadgeLoadingCircle{0%{transform-origin:50%}100%{transform:translate(50%,-50%) rotate(360deg);transform-origin:50%}}.ant-ribbon-wrapper{position:relative}.ant-ribbon{box-sizing:border-box;margin:0;padding:0;color:#6a7580;font-size:13px;font-variant:tabular-nums;line-height:1.38;list-style:none;font-feature-settings:'tnum';position:absolute;top:8px;height:22px;padding:0 8px;color:#fff;line-height:22px;white-space:nowrap;background-color:#0b68ff;border-radius:3px}.ant-ribbon-text{color:#fff}.ant-ribbon-corner{position:absolute;top:100%;width:8px;height:8px;color:currentcolor;border:4px solid;transform:scaleY(.75);transform-origin:top}.ant-ribbon-corner::after{position:absolute;top:-4px;left:-4px;width:inherit;height:inherit;color:rgba(0,0,0,.25);border:inherit;content:''}.ant-ribbon-color-pink{color:#eb2f96;background:#eb2f96}.ant-ribbon-color-magenta{color:#eb2f96;background:#eb2f96}.ant-ribbon-color-red{color:#f5222d;background:#f5222d}.ant-ribbon-color-volcano{color:#fa541c;background:#fa541c}.ant-ribbon-color-orange{color:#fa8c16;background:#fa8c16}.ant-ribbon-color-yellow{color:#fadb14;background:#fadb14}.ant-ribbon-color-gold{color:#faad14;background:#faad14}.ant-ribbon-color-cyan{color:#13c2c2;background:#13c2c2}.ant-ribbon-color-lime{color:#a0d911;background:#a0d911}.ant-ribbon-color-green{color:#52c41a;background:#52c41a}.ant-ribbon-color-blue{color:#1890ff;background:#1890ff}.ant-ribbon-color-geekblue{color:#2f54eb;background:#2f54eb}.ant-ribbon-color-purple{color:#722ed1;background:#722ed1}.ant-ribbon.ant-ribbon-placement-end{right:-8px;border-bottom-right-radius:0}.ant-ribbon.ant-ribbon-placement-end .ant-ribbon-corner{right:0;border-color:currentcolor transparent transparent currentcolor}.ant-ribbon.ant-ribbon-placement-start{left:-8px;border-bottom-left-radius:0}.ant-ribbon.ant-ribbon-placement-start .ant-ribbon-corner{left:0;border-color:currentcolor currentcolor transparent transparent}.ant-badge-rtl{direction:rtl}.ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-badge-count,.ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-badge-dot,.ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-scroll-number-custom-component{right:auto;left:0;direction:ltr;transform:translate(-50%,-50%);transform-origin:0% 0%}.ant-badge-rtl.ant-badge:not(.ant-badge-not-a-wrapper) .ant-scroll-number-custom-component{right:auto;left:0;transform:translate(-50%,-50%);transform-origin:0% 0%}.ant-badge-rtl .ant-badge-status-text{margin-right:8px;margin-left:0}.ant-badge:not(.ant-badge-not-a-wrapper).ant-badge-rtl .ant-badge-zoom-appear,.ant-badge:not(.ant-badge-not-a-wrapper).ant-badge-rtl .ant-badge-zoom-enter{animation-name:antZoomBadgeInRtl}.ant-badge:not(.ant-badge-not-a-wrapper).ant-badge-rtl .ant-badge-zoom-leave{animation-name:antZoomBadgeOutRtl}.ant-ribbon-rtl{direction:rtl}.ant-ribbon-rtl.ant-ribbon-placement-end{right:unset;left:-8px;border-bottom-right-radius:3px;border-bottom-left-radius:0}.ant-ribbon-rtl.ant-ribbon-placement-end .ant-ribbon-corner{right:unset;left:0;border-color:currentcolor currentcolor transparent transparent}.ant-ribbon-rtl.ant-ribbon-placement-end .ant-ribbon-corner::after{border-color:currentcolor currentcolor transparent transparent}.ant-ribbon-rtl.ant-ribbon-placement-start{right:-8px;left:unset;border-bottom-right-radius:0;border-bottom-left-radius:3px}.ant-ribbon-rtl.ant-ribbon-placement-start .ant-ribbon-corner{right:0;left:unset;border-color:currentcolor transparent transparent currentcolor}.ant-ribbon-rtl.ant-ribbon-placement-start .ant-ribbon-corner::after{border-color:currentcolor transparent transparent currentcolor}@keyframes antZoomBadgeInRtl{0%{transform:scale(0) translate(-50%,-50%);opacity:0}100%{transform:scale(1) translate(-50%,-50%)}}@keyframes antZoomBadgeOutRtl{0%{transform:scale(1) translate(-50%,-50%)}100%{transform:scale(0) translate(-50%,-50%);opacity:0}}