@versini/ui-badge 1.0.0
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/README.md +90 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +74 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @versini/ui-badge
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@versini/ui-badge)
|
|
4
|
+
>)
|
|
5
|
+
|
|
6
|
+
> An inline, superscript-style React badge for adding a small red dot or a count to any piece of text.
|
|
7
|
+
|
|
8
|
+
The Badge component renders a `<sup>` element that sits next to text — like a native `<sup>` tag — and displays either a small red dot (notification indicator) or a count (with a `99+` ceiling for large numbers). Its size automatically scales with the surrounding font, so it works inside any text element (`<h1>`, `<p>`, `<button>`, etc.) without manual tuning.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @versini/ui-badge
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
> **Note**: This component requires TailwindCSS and the `@versini/ui-styles` plugin for proper styling. See the [installation documentation](https://versini-org.github.io/ui-components/?path=/docs/getting-started-installation--docs) for complete setup instructions.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Number badge
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { Badge } from "@versini/ui-badge";
|
|
24
|
+
|
|
25
|
+
function App() {
|
|
26
|
+
return (
|
|
27
|
+
<p>
|
|
28
|
+
Notifications <Badge>42</Badge>
|
|
29
|
+
</p>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Dot badge
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { Badge } from "@versini/ui-badge";
|
|
38
|
+
|
|
39
|
+
function App() {
|
|
40
|
+
return (
|
|
41
|
+
<p>
|
|
42
|
+
Profile <Badge />
|
|
43
|
+
</p>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Counts above 99
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<p>
|
|
52
|
+
Inbox <Badge>250</Badge>
|
|
53
|
+
</p>
|
|
54
|
+
// renders "99+"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Auto-scaling with the parent font-size
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
<h1>Messages <Badge>3</Badge></h1>
|
|
61
|
+
<p>Messages <Badge>3</Badge></p>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The badge's font-size and dimensions are expressed in `em` units, so the badge naturally scales with the text it's attached to.
|
|
65
|
+
|
|
66
|
+
### Fine-tuning position
|
|
67
|
+
|
|
68
|
+
Inside flex containers (menu items, buttons with icons, list rows), the default `vertical-align: super` may pull the badge higher than ideal. Use `offsetX` and `offsetY` to nudge it back into place.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// inside a flex menu item — bring the badge down to roughly cap-height
|
|
72
|
+
<MenuItem label={<>Inbox <Badge offsetY="0.4em">12</Badge></>} />
|
|
73
|
+
|
|
74
|
+
// nudge a dot slightly right
|
|
75
|
+
<Badge offsetX="0.2em" />
|
|
76
|
+
|
|
77
|
+
// numbers default to em units (negative shifts up/left)
|
|
78
|
+
<Badge offsetX={0.1} offsetY={-0.1}>42</Badge>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
### Badge Props
|
|
84
|
+
|
|
85
|
+
| Prop | Type | Default | Description |
|
|
86
|
+
| --------- | ------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
87
|
+
| children | `number \| string` | - | The count to display. If omitted, a small red dot is rendered. `0` (number or string) renders nothing. Non-numeric strings also render nothing. |
|
|
88
|
+
| className | `string` | - | CSS class(es) to add to the badge element. |
|
|
89
|
+
| offsetX | `number \| string` | - | Horizontal nudge via CSS `translateX`. Numbers are em units; strings pass through as-is (`"2px"`, `"-0.1em"`). Negative shifts left. |
|
|
90
|
+
| offsetY | `number \| string` | - | Vertical nudge via CSS `translateY`. Same unit rules as `offsetX`. Negative shifts up (toward the superscript position), positive shifts toward the baseline. |
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { JSX } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
export declare const Badge: ({ children, className, offsetX, offsetY, }: BadgeProps) => JSX.Element | null;
|
|
4
|
+
|
|
5
|
+
export declare const BADGE_CLASSNAME = "av-badge";
|
|
6
|
+
|
|
7
|
+
export declare const BADGE_DOT_SR_LABEL = "new";
|
|
8
|
+
|
|
9
|
+
export declare const BADGE_NUMBER_CEILING = 99;
|
|
10
|
+
|
|
11
|
+
declare type BadgeProps = {
|
|
12
|
+
/**
|
|
13
|
+
* The count to display inside the badge. Accepts a number or a numeric
|
|
14
|
+
* string so that the natural JSX form `<Badge>42</Badge>` works.
|
|
15
|
+
* - Omit (or pass `undefined`) to render a small red dot.
|
|
16
|
+
* - A positive number is displayed as-is, with values greater than 99
|
|
17
|
+
* shown as "99+".
|
|
18
|
+
* - `0` renders nothing — matches the ButtonIcon `badge` convention.
|
|
19
|
+
* - Non-numeric values render nothing.
|
|
20
|
+
*/
|
|
21
|
+
children?: number | string;
|
|
22
|
+
/**
|
|
23
|
+
* CSS class(es) to add to the badge element.
|
|
24
|
+
*/
|
|
25
|
+
className?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Horizontal nudge applied via CSS `translateX`. Useful when the badge's
|
|
28
|
+
* default position doesn't quite line up next to surrounding content
|
|
29
|
+
* (e.g. inside a flex menu item, alongside an icon).
|
|
30
|
+
* - Numbers are interpreted as `em` units so the offset scales with the
|
|
31
|
+
* surrounding font (matching the badge's em-based sizing).
|
|
32
|
+
* - Strings are passed through as-is, so you can supply any CSS length
|
|
33
|
+
* (`"2px"`, `"0.25rem"`, `"-0.1em"`).
|
|
34
|
+
* - Negative values shift left.
|
|
35
|
+
*/
|
|
36
|
+
offsetX?: number | string;
|
|
37
|
+
/**
|
|
38
|
+
* Vertical nudge applied via CSS `translateY`. Same unit rules as
|
|
39
|
+
* `offsetX`. Negative values shift up (toward where the badge is
|
|
40
|
+
* pulled by `vertical-align: super`), positive values shift down
|
|
41
|
+
* toward the text baseline.
|
|
42
|
+
*/
|
|
43
|
+
offsetY?: number | string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
@versini/ui-badge v1.0.0
|
|
3
|
+
© 2026 gizmette.com
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
import clsx from "clsx";
|
|
8
|
+
|
|
9
|
+
const BADGE_CLASSNAME = "av-badge";
|
|
10
|
+
const BADGE_NUMBER_CEILING = 99;
|
|
11
|
+
const BADGE_DOT_SR_LABEL = "new";
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const getBadgeDisplayValue = (value)=>value > (/* inlined export .BADGE_NUMBER_CEILING */99) ? `${(/* inlined export .BADGE_NUMBER_CEILING */99)}+` : String(value);
|
|
18
|
+
const toCssLength = (value)=>typeof value === "number" ? `${value}em` : value;
|
|
19
|
+
const getBadgeOffsetStyle = ({ offsetX, offsetY })=>{
|
|
20
|
+
if (offsetX === undefined && offsetY === undefined) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const x = offsetX === undefined ? "0" : toCssLength(offsetX);
|
|
24
|
+
const y = offsetY === undefined ? "0" : toCssLength(offsetY);
|
|
25
|
+
return {
|
|
26
|
+
transform: `translate(${x}, ${y})`
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const getBadgeClasses = ({ isDot, className })=>clsx(BADGE_CLASSNAME, "inline-flex items-center justify-center", "align-super leading-none", "rounded-full", "border border-border-white", "bg-copy-error-dark text-copy-lighter", "pointer-events-none select-none", {
|
|
30
|
+
"size-[0.6em]": isDot,
|
|
31
|
+
"text-[0.65em] font-semibold min-w-[1.5em] h-[1.5em] px-[0.4em] whitespace-nowrap": !isDot
|
|
32
|
+
}, className);
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
const Badge = ({ children, className, offsetX, offsetY })=>{
|
|
38
|
+
const style = getBadgeOffsetStyle({
|
|
39
|
+
offsetX,
|
|
40
|
+
offsetY
|
|
41
|
+
});
|
|
42
|
+
if (children === undefined) {
|
|
43
|
+
return /*#__PURE__*/ jsx("sup", {
|
|
44
|
+
className: getBadgeClasses({
|
|
45
|
+
isDot: true,
|
|
46
|
+
className
|
|
47
|
+
}),
|
|
48
|
+
style: style,
|
|
49
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
50
|
+
className: "sr-only",
|
|
51
|
+
children: (/* inlined export .BADGE_DOT_SR_LABEL */"new")
|
|
52
|
+
})
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const numericValue = typeof children === "number" ? children : Number(children);
|
|
56
|
+
if (!Number.isFinite(numericValue) || numericValue === 0) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return /*#__PURE__*/ jsx("sup", {
|
|
60
|
+
className: getBadgeClasses({
|
|
61
|
+
isDot: false,
|
|
62
|
+
className
|
|
63
|
+
}),
|
|
64
|
+
style: style,
|
|
65
|
+
children: getBadgeDisplayValue(numericValue)
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
var components_BADGE_DOT_SR_LABEL = (/* inlined export .BADGE_DOT_SR_LABEL */"new");
|
|
73
|
+
var components_BADGE_NUMBER_CEILING = (/* inlined export .BADGE_NUMBER_CEILING */99);
|
|
74
|
+
export { BADGE_CLASSNAME, Badge, components_BADGE_DOT_SR_LABEL as BADGE_DOT_SR_LABEL, components_BADGE_NUMBER_CEILING as BADGE_NUMBER_CEILING };
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@versini/ui-badge",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"author": "Arno Versini",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://www.npmjs.com/package/@versini/ui-badge",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git@github.com:aversini/ui-components.git"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "dist/index.js",
|
|
16
|
+
"types": "dist/index.d.ts",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build:check": "tsc",
|
|
23
|
+
"build:js": "rslib build",
|
|
24
|
+
"build:types": "echo 'Types now built with rslib'",
|
|
25
|
+
"build": "npm-run-all --serial clean build:check build:js",
|
|
26
|
+
"clean": "rimraf dist tmp coverage",
|
|
27
|
+
"dev:js": "rslib build --watch",
|
|
28
|
+
"dev:types": "echo 'Types now watched with rslib'",
|
|
29
|
+
"dev": "rslib build --watch",
|
|
30
|
+
"lint": "biome lint src",
|
|
31
|
+
"lint:fix": "biome check src --write --no-errors-on-unmatched",
|
|
32
|
+
"prettier": "biome check --write --no-errors-on-unmatched",
|
|
33
|
+
"start": "static-server dist --port 5173",
|
|
34
|
+
"test:coverage:ui": "vitest --coverage --ui",
|
|
35
|
+
"test:coverage": "vitest run --coverage",
|
|
36
|
+
"test:watch": "vitest",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:visual": "playwright test -c playwright-ct.config.ts",
|
|
39
|
+
"test:visual:report": "playwright show-report playwright-report",
|
|
40
|
+
"test:visual:update": "playwright test -c playwright-ct.config.ts --update-snapshots",
|
|
41
|
+
"test:visual:ui": "playwright test -c playwright-ct.config.ts --ui"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@versini/ui-types": "workspace:*"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"clsx": "2.1.1",
|
|
48
|
+
"tailwindcss": "4.3.0"
|
|
49
|
+
},
|
|
50
|
+
"sideEffects": [
|
|
51
|
+
"**/*.css"
|
|
52
|
+
]
|
|
53
|
+
}
|