react-native-terra-ui 0.11.0 → 0.11.1

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/lib/module/components/badge/Badge.js +174 -0
  3. package/lib/module/components/badge/Badge.js.map +1 -0
  4. package/lib/module/components/badge/index.js +4 -0
  5. package/lib/module/components/badge/index.js.map +1 -0
  6. package/lib/module/components/badge/parts/BadgeIndicator.js +191 -0
  7. package/lib/module/components/badge/parts/BadgeIndicator.js.map +1 -0
  8. package/lib/module/components/badge/types.js +4 -0
  9. package/lib/module/components/badge/types.js.map +1 -0
  10. package/lib/module/components/badge/utils.js +25 -0
  11. package/lib/module/components/badge/utils.js.map +1 -0
  12. package/lib/module/components/index.js +1 -0
  13. package/lib/module/components/index.js.map +1 -1
  14. package/lib/typescript/src/components/badge/Badge.d.ts +16 -0
  15. package/lib/typescript/src/components/badge/Badge.d.ts.map +1 -0
  16. package/lib/typescript/src/components/badge/index.d.ts +3 -0
  17. package/lib/typescript/src/components/badge/index.d.ts.map +1 -0
  18. package/lib/typescript/src/components/badge/parts/BadgeIndicator.d.ts +19 -0
  19. package/lib/typescript/src/components/badge/parts/BadgeIndicator.d.ts.map +1 -0
  20. package/lib/typescript/src/components/badge/types.d.ts +48 -0
  21. package/lib/typescript/src/components/badge/types.d.ts.map +1 -0
  22. package/lib/typescript/src/components/badge/utils.d.ts +13 -0
  23. package/lib/typescript/src/components/badge/utils.d.ts.map +1 -0
  24. package/lib/typescript/src/components/index.d.ts +1 -0
  25. package/lib/typescript/src/components/index.d.ts.map +1 -1
  26. package/package.json +1 -1
  27. package/src/components/badge/Badge.tsx +111 -0
  28. package/src/components/badge/index.ts +2 -0
  29. package/src/components/badge/parts/BadgeIndicator.tsx +166 -0
  30. package/src/components/badge/types.ts +58 -0
  31. package/src/components/badge/utils.ts +24 -0
  32. package/src/components/index.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.1](https://github.com/earthlin9/react-native-terra-ui/compare/v0.11.0...v0.11.1) (2026-09-12)
4
+
5
+ ### Features
6
+
7
+ * **ui:** [badge] add Badge wrapper component ([4c3f49c](https://github.com/earthlin9/react-native-terra-ui/commit/4c3f49c9725f77bf4cbd479094335d710190d978))
8
+
3
9
  ## [0.11.0](https://github.com/earthlin9/react-native-terra-ui/compare/v0.10.2...v0.11.0) (2026-09-12)
4
10
 
5
11
  ### ⚠ BREAKING CHANGES
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+
3
+ import { View } from "react-native-unistyles/components/native/View";
4
+ import { forwardRef } from "react";
5
+ import { StyleSheet } from "react-native-unistyles";
6
+ import { BadgeIndicator } from "./parts/BadgeIndicator.js";
7
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
+ /**
9
+ * Overlays a count or status indicator on one corner of the element it wraps.
10
+ *
11
+ * ```tsx
12
+ * <Badge placement="top-right" color="danger" label={3}>
13
+ * <Toolbar.Button icon="bell" accessibilityLabel="Notifications" />
14
+ * </Badge>
15
+ * ```
16
+ *
17
+ * The child renders untouched — `Badge` contributes the positioning context and
18
+ * the overlay, nothing else.
19
+ */
20
+ const Badge = /*#__PURE__*/forwardRef(function Badge({
21
+ children,
22
+ placement = "top-right",
23
+ label,
24
+ color = "danger",
25
+ size = "md",
26
+ isVisible = true,
27
+ fullWidth = false,
28
+ accessibilityLabel,
29
+ style,
30
+ ...rest
31
+ }, ref) {
32
+ const _styles = styles;
33
+ {
34
+ const styles = _styles.useVariants({
35
+ placement,
36
+ size,
37
+ fullWidth
38
+ });
39
+ return (
40
+ /*#__PURE__*/
41
+ // `style` last so an explicit `alignSelf` beats `fullWidth`.
42
+ _jsxs(View, {
43
+ ref: ref,
44
+ style: [styles.wrapper, style],
45
+ ...rest,
46
+ children: [children, isVisible ? /*#__PURE__*/_jsx(View, {
47
+ pointerEvents: "box-none",
48
+ style: styles.anchor,
49
+ children: /*#__PURE__*/_jsx(BadgeIndicator, {
50
+ label: label,
51
+ color: color,
52
+ size: size,
53
+ accessibilityLabel: accessibilityLabel
54
+ })
55
+ }) : null]
56
+ })
57
+ );
58
+ }
59
+ });
60
+ export { Badge };
61
+
62
+ // ─── Styles ───────────────────────────────────────────────────────────────────
63
+ //
64
+ // The indicator is positioned by absolute insets picked from `size`, never by
65
+ // measuring the child — its overhang is a function of its own size, not the
66
+ // child's. Which edges those insets land on is `placement`, and the magnitude
67
+ // is `size`, so the two interact and the offsets are compound variants.
68
+ //
69
+ // The inset equals the dot's radius, which puts a dot's centre exactly on the
70
+ // child's corner. A pill keeps the same anchored edges and grows inward from
71
+ // them, so a longer label extends across the child rather than away from it.
72
+ const styles = StyleSheet.create(() => ({
73
+ wrapper: {
74
+ position: "relative",
75
+ variants: {
76
+ // `alignSelf: 'flex-start'` hugs the child, so the indicator anchors to
77
+ // the child's bounds rather than the surrounding container's. A child
78
+ // that was stretching needs `fullWidth` to go on doing so.
79
+ fullWidth: {
80
+ true: {
81
+ alignSelf: "stretch"
82
+ },
83
+ false: {
84
+ alignSelf: "flex-start"
85
+ }
86
+ },
87
+ placement: {
88
+ "top-right": {},
89
+ "top-left": {},
90
+ "bottom-right": {},
91
+ "bottom-left": {}
92
+ },
93
+ size: {
94
+ sm: {},
95
+ md: {}
96
+ }
97
+ },
98
+ uni__dependencies: [4]
99
+ },
100
+ anchor: {
101
+ position: "absolute",
102
+ variants: {
103
+ placement: {
104
+ "top-right": {},
105
+ "top-left": {},
106
+ "bottom-right": {},
107
+ "bottom-left": {}
108
+ },
109
+ size: {
110
+ sm: {},
111
+ md: {}
112
+ }
113
+ },
114
+ compoundVariants: [{
115
+ placement: "top-right",
116
+ size: "sm",
117
+ styles: {
118
+ top: -5,
119
+ right: -5
120
+ }
121
+ }, {
122
+ placement: "top-right",
123
+ size: "md",
124
+ styles: {
125
+ top: -6,
126
+ right: -6
127
+ }
128
+ }, {
129
+ placement: "top-left",
130
+ size: "sm",
131
+ styles: {
132
+ top: -5,
133
+ left: -5
134
+ }
135
+ }, {
136
+ placement: "top-left",
137
+ size: "md",
138
+ styles: {
139
+ top: -6,
140
+ left: -6
141
+ }
142
+ }, {
143
+ placement: "bottom-right",
144
+ size: "sm",
145
+ styles: {
146
+ bottom: -5,
147
+ right: -5
148
+ }
149
+ }, {
150
+ placement: "bottom-right",
151
+ size: "md",
152
+ styles: {
153
+ bottom: -6,
154
+ right: -6
155
+ }
156
+ }, {
157
+ placement: "bottom-left",
158
+ size: "sm",
159
+ styles: {
160
+ bottom: -5,
161
+ left: -5
162
+ }
163
+ }, {
164
+ placement: "bottom-left",
165
+ size: "md",
166
+ styles: {
167
+ bottom: -6,
168
+ left: -6
169
+ }
170
+ }],
171
+ uni__dependencies: [4]
172
+ }
173
+ }));
174
+ //# sourceMappingURL=Badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["forwardRef","StyleSheet","BadgeIndicator","jsx","_jsx","jsxs","_jsxs","Badge","children","placement","label","color","size","isVisible","fullWidth","accessibilityLabel","style","rest","ref","_styles","styles","useVariants","View","wrapper","pointerEvents","anchor","create","position","variants","true","alignSelf","false","sm","md","uni__dependencies","compoundVariants","top","right","left","bottom"],"sourceRoot":"../../../../src","sources":["components/badge/Badge.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ export { Badge } from "./Badge.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["Badge"],"sourceRoot":"../../../../src","sources":["components/badge/index.ts"],"mappings":";;AAAA,SAASA,KAAK","ignoreList":[]}
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+
3
+ import { View } from "react-native-unistyles/components/native/View";
4
+ import { Text as RNText } from "react-native-unistyles/components/native/Text";
5
+ import { StyleSheet, useUnistyles } from "react-native-unistyles";
6
+ import { resolveThemeColor } from "../../../utils/resolve-theme-color.js";
7
+ import { FONT_WEIGHT_VALUE, SYSTEM_FONT } from "../../../utils/typography.js";
8
+ import { getCustomBadgeColors, hasLabel } from "../utils.js";
9
+ import { jsx as _jsx } from "react/jsx-runtime";
10
+ const SCHEME_NAMES = new Set(["primary", "secondary", "success", "warning", "danger"]);
11
+ /** Label foreground per scheme — mirrors the spec's token table. */
12
+ const FG_TOKEN = {
13
+ primary: "accent.solid.fg",
14
+ secondary: "neutral.solid.fg",
15
+ success: "success.solid.fg",
16
+ warning: "warning.solid.fg",
17
+ danger: "danger.solid.fg"
18
+ };
19
+ const LABEL_VARIANT = {
20
+ sm: "label-sm",
21
+ md: "label-md"
22
+ };
23
+ /**
24
+ * The overlay itself: a dot, or a pill carrying the label. Owns the paint, the
25
+ * legibility ring, and the accessibility node — the wrapper owns only position.
26
+ *
27
+ * `useUnistyles` is called unconditionally, which is what subscribes this
28
+ * component to theme changes. The background below comes from a variant, which
29
+ * unistyles applies straight to the native node on a theme switch; without a
30
+ * re-render React would go on believing the node holds the previous theme's
31
+ * value. `Chip` documents the same hazard on its root.
32
+ */
33
+ export const BadgeIndicator = ({
34
+ label,
35
+ color,
36
+ size,
37
+ accessibilityLabel
38
+ }) => {
39
+ const {
40
+ theme
41
+ } = useUnistyles();
42
+ // A literal never matches one of the five variant options, so it's cast to
43
+ // satisfy `useVariants`'s literal-union type while still being compared
44
+ // against the real value below.
45
+ const _styles = styles;
46
+ {
47
+ const styles = _styles.useVariants({
48
+ color: color,
49
+ size
50
+ });
51
+ const custom = SCHEME_NAMES.has(color) ? null : getCustomBadgeColors(color);
52
+ const labelled = hasLabel(label);
53
+ const foreground = custom ? custom.fg : FG_TOKEN[color];
54
+
55
+ // An explicit label wins even on a dot; a dot with nothing to announce is
56
+ // decorative and hidden rather than read out as an unlabelled element.
57
+ const announced = accessibilityLabel ?? (labelled ? String(label) : undefined);
58
+ const accessibility = announced ? {
59
+ accessible: true,
60
+ accessibilityLabel: announced
61
+ } : {
62
+ accessibilityElementsHidden: true,
63
+ importantForAccessibility: "no-hide-descendants",
64
+ "aria-hidden": true
65
+ };
66
+ return /*#__PURE__*/_jsx(View, {
67
+ style: [styles.indicator, labelled ? styles.pill : styles.dot, custom ? {
68
+ backgroundColor: custom.bg
69
+ } : null],
70
+ ...accessibility,
71
+ children: labelled ? /*#__PURE__*/_jsx(RNText, {
72
+ style: [styles.label, {
73
+ color: resolveThemeColor(foreground, theme)
74
+ }],
75
+ maxFontSizeMultiplier: theme.typography.variants[LABEL_VARIANT[size]].maxFontSizeMultiplier,
76
+ numberOfLines: 1,
77
+ children: label
78
+ }) : null
79
+ });
80
+ }
81
+ };
82
+
83
+ // ─── Styles ───────────────────────────────────────────────────────────────────
84
+ //
85
+ // `color` is a plain variant rather than a compound one: `Badge` renders solid
86
+ // only, so there is no second paint axis for it to interact with.
87
+ //
88
+ // React Native's box model is border-box, so the ring below eats into the
89
+ // indicator rather than growing it — the dot's outer diameter stays exactly
90
+ // what `size` declares, with no compensation arithmetic.
91
+ const styles = StyleSheet.create(theme => {
92
+ const c = theme.color;
93
+ // A custom font encodes its weight in the family name, so only the system
94
+ // font takes a numeric `fontWeight` — the same branch `Chip` makes. Inlined
95
+ // rather than extracted: the unistyles Babel plugin rewrites this factory,
96
+ // and an imported function called from inside it is not in scope at runtime.
97
+ const labelFont = theme.typography.fonts.semibold;
98
+ const isSystemLabelFont = labelFont === SYSTEM_FONT;
99
+ return {
100
+ indicator: {
101
+ alignItems: "center",
102
+ justifyContent: "center",
103
+ borderRadius: theme.radius.full,
104
+ borderColor: c["surface.canvas"],
105
+ variants: {
106
+ size: {
107
+ sm: {
108
+ borderWidth: 1.5
109
+ },
110
+ md: {
111
+ borderWidth: 2
112
+ }
113
+ },
114
+ color: {
115
+ primary: {
116
+ backgroundColor: c["accent.solid.bg"]
117
+ },
118
+ secondary: {
119
+ backgroundColor: c["neutral.solid.bg"]
120
+ },
121
+ success: {
122
+ backgroundColor: c["success.solid.bg"]
123
+ },
124
+ warning: {
125
+ backgroundColor: c["warning.solid.bg"]
126
+ },
127
+ danger: {
128
+ backgroundColor: c["danger.solid.bg"]
129
+ }
130
+ }
131
+ },
132
+ uni__dependencies: [0, 4]
133
+ },
134
+ dot: {
135
+ variants: {
136
+ size: {
137
+ sm: {
138
+ width: 10,
139
+ height: 10
140
+ },
141
+ md: {
142
+ width: 12,
143
+ height: 12
144
+ }
145
+ }
146
+ },
147
+ uni__dependencies: [4]
148
+ },
149
+ pill: {
150
+ variants: {
151
+ size: {
152
+ sm: {
153
+ height: 16,
154
+ minWidth: 16,
155
+ paddingHorizontal: 4
156
+ },
157
+ md: {
158
+ height: 20,
159
+ minWidth: 20,
160
+ paddingHorizontal: 6
161
+ }
162
+ }
163
+ },
164
+ uni__dependencies: [4]
165
+ },
166
+ // Spelled out rather than derived from `LABEL_VARIANT`: the unistyles Babel
167
+ // plugin reads this object statically, so computed variant keys are not
168
+ // safe here even though they would resolve correctly under test.
169
+ label: {
170
+ textAlign: "center",
171
+ fontFamily: isSystemLabelFont ? undefined : labelFont,
172
+ fontWeight: isSystemLabelFont ? FONT_WEIGHT_VALUE.semibold : undefined,
173
+ variants: {
174
+ size: {
175
+ sm: {
176
+ fontSize: theme.typography.variants["label-sm"].fontSize,
177
+ lineHeight: theme.typography.variants["label-sm"].lineHeight,
178
+ letterSpacing: theme.typography.variants["label-sm"].letterSpacing
179
+ },
180
+ md: {
181
+ fontSize: theme.typography.variants["label-md"].fontSize,
182
+ lineHeight: theme.typography.variants["label-md"].lineHeight,
183
+ letterSpacing: theme.typography.variants["label-md"].letterSpacing
184
+ }
185
+ }
186
+ },
187
+ uni__dependencies: [0, 4]
188
+ }
189
+ };
190
+ });
191
+ //# sourceMappingURL=BadgeIndicator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["StyleSheet","useUnistyles","resolveThemeColor","FONT_WEIGHT_VALUE","SYSTEM_FONT","getCustomBadgeColors","hasLabel","jsx","_jsx","SCHEME_NAMES","Set","FG_TOKEN","primary","secondary","success","warning","danger","LABEL_VARIANT","sm","md","BadgeIndicator","label","color","size","accessibilityLabel","theme","_styles","styles","useVariants","custom","has","labelled","foreground","fg","announced","String","undefined","accessibility","accessible","accessibilityElementsHidden","importantForAccessibility","View","style","indicator","pill","dot","backgroundColor","bg","children","RNText","maxFontSizeMultiplier","typography","variants","numberOfLines","create","c","labelFont","fonts","semibold","isSystemLabelFont","alignItems","justifyContent","borderRadius","radius","full","borderColor","borderWidth","uni__dependencies","width","height","minWidth","paddingHorizontal","textAlign","fontFamily","fontWeight","fontSize","lineHeight","letterSpacing"],"sourceRoot":"../../../../../src","sources":["components/badge/parts/BadgeIndicator.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../../../src","sources":["components/badge/types.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ import { readableOn } from "../../utils/color-utils.js";
4
+
5
+ // A custom color literal (hex/rgb/hsl) isn't a bounded set of options, so it
6
+ // can't be a unistyles variant condition — it's derived here instead, the same
7
+ // way `chip/utils.ts` handles the equivalent case on `Chip`.
8
+
9
+ /** The indicator's paint for a raw literal: the literal itself, plus a readable label. */
10
+ export function getCustomBadgeColors(literal) {
11
+ return {
12
+ bg: literal,
13
+ fg: readableOn(literal)
14
+ };
15
+ }
16
+
17
+ /**
18
+ * `true` when `label` should render as a pill. An empty string and `undefined`
19
+ * are a dot; `0` is a label, since a caller suppressing a zero count uses
20
+ * `isVisible` rather than relying on falsiness.
21
+ */
22
+ export function hasLabel(label) {
23
+ return label !== undefined && label !== "";
24
+ }
25
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["readableOn","getCustomBadgeColors","literal","bg","fg","hasLabel","label","undefined"],"sourceRoot":"../../../../src","sources":["components/badge/utils.ts"],"mappings":";;AAAA,SAASA,UAAU;;AAEnB;AACA;AACA;;AAOA;AACA,OAAO,SAASC,oBAAoBA,CAACC,OAAe,EAAqB;EACvE,OAAO;IAAEC,EAAE,EAAED,OAAO;IAAEE,EAAE,EAAEJ,UAAU,CAACE,OAAO;EAAE,CAAC;AACjD;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASG,QAAQA,CAACC,KAAkC,EAA4B;EACrF,OAAOA,KAAK,KAAKC,SAAS,IAAID,KAAK,KAAK,EAAE;AAC5C","ignoreList":[]}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  export * from "./avatar/index.js";
4
+ export * from "./badge/index.js";
4
5
  export * from "./box/index.js";
5
6
  export * from "./button/index.js";
6
7
  export * from "./chip/index.js";
@@ -1 +1 @@
1
- {"version":3,"names":[],"sourceRoot":"../../../src","sources":["components/index.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","ignoreList":[]}
1
+ {"version":3,"names":[],"sourceRoot":"../../../src","sources":["components/index.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","ignoreList":[]}
@@ -0,0 +1,16 @@
1
+ import type { BadgeProps } from "./types.js";
2
+ /**
3
+ * Overlays a count or status indicator on one corner of the element it wraps.
4
+ *
5
+ * ```tsx
6
+ * <Badge placement="top-right" color="danger" label={3}>
7
+ * <Toolbar.Button icon="bell" accessibilityLabel="Notifications" />
8
+ * </Badge>
9
+ * ```
10
+ *
11
+ * The child renders untouched — `Badge` contributes the positioning context and
12
+ * the overlay, nothing else.
13
+ */
14
+ declare const Badge: import("react").ForwardRefExoticComponent<BadgeProps & import("react").RefAttributes<import("react-native/types_generated/src/private/webapis/dom/nodes/ReactNativeElement").default>>;
15
+ export { Badge };
16
+ //# sourceMappingURL=Badge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Badge.d.ts","sourceRoot":"","sources":["../../../../../src/components/badge/Badge.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAS,CAAC;AAE1C;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,KAAK,wLA4BT,CAAC;AAEH,OAAO,EAAE,KAAK,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { Badge } from "./Badge.js";
2
+ export type { BadgeColor, BadgePlacement, BadgeProps, BadgeSize } from "./types.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/badge/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAS,CAAC;AAChC,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAS,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { BadgeColor, BadgeSize } from "../types.js";
2
+ export interface BadgeIndicatorProps {
3
+ label?: string | number;
4
+ color: BadgeColor;
5
+ size: BadgeSize;
6
+ accessibilityLabel?: string;
7
+ }
8
+ /**
9
+ * The overlay itself: a dot, or a pill carrying the label. Owns the paint, the
10
+ * legibility ring, and the accessibility node — the wrapper owns only position.
11
+ *
12
+ * `useUnistyles` is called unconditionally, which is what subscribes this
13
+ * component to theme changes. The background below comes from a variant, which
14
+ * unistyles applies straight to the native node on a theme switch; without a
15
+ * re-render React would go on believing the node holds the previous theme's
16
+ * value. `Chip` documents the same hazard on its root.
17
+ */
18
+ export declare const BadgeIndicator: ({ label, color, size, accessibilityLabel }: BadgeIndicatorProps) => import("react").JSX.Element;
19
+ //# sourceMappingURL=BadgeIndicator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BadgeIndicator.d.ts","sourceRoot":"","sources":["../../../../../../src/components/badge/parts/BadgeIndicator.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAU,CAAC;AAqBtD,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAI,4CAA4C,mBAAmB,gCA0C7F,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ViewProps } from "react-native";
3
+ /** Corner of the wrapped child the indicator is anchored to. */
4
+ export type BadgePlacement = "top-right" | "top-left" | "bottom-right" | "bottom-left";
5
+ /** Scale of the dot, pill, and label. */
6
+ export type BadgeSize = "sm" | "md";
7
+ /**
8
+ * Semantic color scheme, or a raw CSS color literal (`#fff`, `rgb(...)`, `hsl(...)`).
9
+ * A literal paints the indicator with that exact value and derives a readable
10
+ * foreground for the label.
11
+ */
12
+ export type BadgeColor = "primary" | "secondary" | "success" | "warning" | "danger" | (string & {});
13
+ export interface BadgeProps extends ViewProps {
14
+ /** The element being badged. Rendered untouched; `Badge` only adds the overlay. */
15
+ children: ReactNode;
16
+ /**
17
+ * Corner the indicator sits on. Defaults to `'top-right'`.
18
+ *
19
+ * The indicator overhangs the child, so an ancestor that clips — a `Surface`
20
+ * with a radius, or a `Screen` list row — will cut it off. Pad that container
21
+ * by the overhang, or lift the `Badge` outside it.
22
+ */
23
+ placement?: BadgePlacement;
24
+ /**
25
+ * Text in the indicator. Omit it (or pass an empty string) for a dot.
26
+ * `0` renders as a label — use `isVisible` to suppress a zero count.
27
+ */
28
+ label?: string | number;
29
+ /** Semantic scheme or raw color literal. Defaults to `'danger'`. */
30
+ color?: BadgeColor;
31
+ /** Scale of the indicator. Defaults to `'md'`. */
32
+ size?: BadgeSize;
33
+ /** Renders the indicator. The child renders either way. Defaults to `true`. */
34
+ isVisible?: boolean;
35
+ /**
36
+ * Stretches the wrapper along its parent's cross axis, so a child that
37
+ * stretches unwrapped keeps stretching. Defaults to `false`, which hugs the
38
+ * child — what an icon, avatar, or toolbar button needs. An explicit
39
+ * `alignSelf` in `style` beats both.
40
+ */
41
+ fullWidth?: boolean;
42
+ /**
43
+ * Announced in place of the label. Defaults to the label's own text; an
44
+ * unlabelled dot is hidden from assistive technology.
45
+ */
46
+ accessibilityLabel?: string;
47
+ }
48
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/components/badge/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,gEAAgE;AAChE,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,aAAa,CAAC;AAEvF,yCAAyC;AACzC,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,CAAC;AAEpC;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,WAAW,GACX,SAAS,GACT,SAAS,GACT,QAAQ,GAER,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,mFAAmF;IACnF,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,oEAAoE;IACpE,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,kDAAkD;IAClD,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B"}
@@ -0,0 +1,13 @@
1
+ export interface CustomBadgeColors {
2
+ bg: string;
3
+ fg: string;
4
+ }
5
+ /** The indicator's paint for a raw literal: the literal itself, plus a readable label. */
6
+ export declare function getCustomBadgeColors(literal: string): CustomBadgeColors;
7
+ /**
8
+ * `true` when `label` should render as a pill. An empty string and `undefined`
9
+ * are a dot; `0` is a label, since a caller suppressing a zero count uses
10
+ * `isVisible` rather than relying on falsiness.
11
+ */
12
+ export declare function hasLabel(label: string | number | undefined): label is string | number;
13
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/components/badge/utils.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,0FAA0F;AAC1F,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAEvE;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,MAAM,GAAG,MAAM,CAErF"}
@@ -1,4 +1,5 @@
1
1
  export * from "./avatar/index.js";
2
+ export * from "./badge/index.js";
2
3
  export * from "./box/index.js";
3
4
  export * from "./button/index.js";
4
5
  export * from "./chip/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAU,CAAC;AACzB,cAAc,gBAAO,CAAC;AACtB,cAAc,mBAAU,CAAC;AACzB,cAAc,iBAAQ,CAAC;AACvB,cAAc,oBAAW,CAAC;AAC1B,cAAc,wBAAe,CAAC;AAC9B,cAAc,mBAAU,CAAC;AACzB,cAAc,iBAAQ,CAAC;AACvB,cAAc,kBAAS,CAAC;AACxB,cAAc,2BAAkB,CAAC;AACjC,cAAc,mBAAU,CAAC;AACzB,cAAc,sBAAa,CAAC;AAC5B,cAAc,mBAAU,CAAC;AACzB,cAAc,iBAAQ,CAAC;AACvB,cAAc,oBAAW,CAAC;AAC1B,cAAc,oBAAW,CAAC;AAC1B,cAAc,iBAAQ,CAAC;AACvB,cAAc,kBAAS,CAAC;AACxB,cAAc,oBAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAU,CAAC;AACzB,cAAc,kBAAS,CAAC;AACxB,cAAc,gBAAO,CAAC;AACtB,cAAc,mBAAU,CAAC;AACzB,cAAc,iBAAQ,CAAC;AACvB,cAAc,oBAAW,CAAC;AAC1B,cAAc,wBAAe,CAAC;AAC9B,cAAc,mBAAU,CAAC;AACzB,cAAc,iBAAQ,CAAC;AACvB,cAAc,kBAAS,CAAC;AACxB,cAAc,2BAAkB,CAAC;AACjC,cAAc,mBAAU,CAAC;AACzB,cAAc,sBAAa,CAAC;AAC5B,cAAc,mBAAU,CAAC;AACzB,cAAc,iBAAQ,CAAC;AACvB,cAAc,oBAAW,CAAC;AAC1B,cAAc,oBAAW,CAAC;AAC1B,cAAc,iBAAQ,CAAC;AACvB,cAAc,kBAAS,CAAC;AACxB,cAAc,oBAAW,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-terra-ui",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Themeable React Native UI components built on react-native-unistyles",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -0,0 +1,111 @@
1
+ import { type ComponentRef, forwardRef } from "react";
2
+ import { View } from "react-native";
3
+
4
+ import { StyleSheet } from "react-native-unistyles";
5
+
6
+ import { BadgeIndicator } from "./parts/BadgeIndicator";
7
+ import type { BadgeProps } from "./types";
8
+
9
+ /**
10
+ * Overlays a count or status indicator on one corner of the element it wraps.
11
+ *
12
+ * ```tsx
13
+ * <Badge placement="top-right" color="danger" label={3}>
14
+ * <Toolbar.Button icon="bell" accessibilityLabel="Notifications" />
15
+ * </Badge>
16
+ * ```
17
+ *
18
+ * The child renders untouched — `Badge` contributes the positioning context and
19
+ * the overlay, nothing else.
20
+ */
21
+ const Badge = forwardRef<ComponentRef<typeof View>, BadgeProps>(function Badge(
22
+ {
23
+ children,
24
+ placement = "top-right",
25
+ label,
26
+ color = "danger",
27
+ size = "md",
28
+ isVisible = true,
29
+ fullWidth = false,
30
+ accessibilityLabel,
31
+ style,
32
+ ...rest
33
+ },
34
+ ref
35
+ ) {
36
+ styles.useVariants({ placement, size, fullWidth });
37
+
38
+ return (
39
+ // `style` last so an explicit `alignSelf` beats `fullWidth`.
40
+ <View ref={ref} style={[styles.wrapper, style]} {...rest}>
41
+ {children}
42
+ {isVisible ? (
43
+ <View pointerEvents="box-none" style={styles.anchor}>
44
+ <BadgeIndicator label={label} color={color} size={size} accessibilityLabel={accessibilityLabel} />
45
+ </View>
46
+ ) : null}
47
+ </View>
48
+ );
49
+ });
50
+
51
+ export { Badge };
52
+
53
+ // ─── Styles ───────────────────────────────────────────────────────────────────
54
+ //
55
+ // The indicator is positioned by absolute insets picked from `size`, never by
56
+ // measuring the child — its overhang is a function of its own size, not the
57
+ // child's. Which edges those insets land on is `placement`, and the magnitude
58
+ // is `size`, so the two interact and the offsets are compound variants.
59
+ //
60
+ // The inset equals the dot's radius, which puts a dot's centre exactly on the
61
+ // child's corner. A pill keeps the same anchored edges and grows inward from
62
+ // them, so a longer label extends across the child rather than away from it.
63
+ const styles = StyleSheet.create(() => ({
64
+ wrapper: {
65
+ position: "relative",
66
+ variants: {
67
+ // `alignSelf: 'flex-start'` hugs the child, so the indicator anchors to
68
+ // the child's bounds rather than the surrounding container's. A child
69
+ // that was stretching needs `fullWidth` to go on doing so.
70
+ fullWidth: {
71
+ true: { alignSelf: "stretch" },
72
+ false: { alignSelf: "flex-start" },
73
+ },
74
+ placement: {
75
+ "top-right": {},
76
+ "top-left": {},
77
+ "bottom-right": {},
78
+ "bottom-left": {},
79
+ },
80
+ size: {
81
+ sm: {},
82
+ md: {},
83
+ },
84
+ },
85
+ },
86
+ anchor: {
87
+ position: "absolute",
88
+ variants: {
89
+ placement: {
90
+ "top-right": {},
91
+ "top-left": {},
92
+ "bottom-right": {},
93
+ "bottom-left": {},
94
+ },
95
+ size: {
96
+ sm: {},
97
+ md: {},
98
+ },
99
+ },
100
+ compoundVariants: [
101
+ { placement: "top-right", size: "sm", styles: { top: -5, right: -5 } },
102
+ { placement: "top-right", size: "md", styles: { top: -6, right: -6 } },
103
+ { placement: "top-left", size: "sm", styles: { top: -5, left: -5 } },
104
+ { placement: "top-left", size: "md", styles: { top: -6, left: -6 } },
105
+ { placement: "bottom-right", size: "sm", styles: { bottom: -5, right: -5 } },
106
+ { placement: "bottom-right", size: "md", styles: { bottom: -6, right: -6 } },
107
+ { placement: "bottom-left", size: "sm", styles: { bottom: -5, left: -5 } },
108
+ { placement: "bottom-left", size: "md", styles: { bottom: -6, left: -6 } },
109
+ ],
110
+ },
111
+ }));
@@ -0,0 +1,2 @@
1
+ export { Badge } from "./Badge";
2
+ export type { BadgeColor, BadgePlacement, BadgeProps, BadgeSize } from "./types";
@@ -0,0 +1,166 @@
1
+ import { Text as RNText, View } from "react-native";
2
+
3
+ import { StyleSheet, useUnistyles } from "react-native-unistyles";
4
+
5
+ import type { ColorToken, RoleTextVariant } from "#theme/types";
6
+ import { resolveThemeColor } from "#utils/resolve-theme-color";
7
+ import { FONT_WEIGHT_VALUE, SYSTEM_FONT } from "#utils/typography";
8
+ import type { BadgeColor, BadgeSize } from "../types";
9
+ import { getCustomBadgeColors, hasLabel } from "../utils";
10
+
11
+ const SCHEME_NAMES = new Set(["primary", "secondary", "success", "warning", "danger"]);
12
+
13
+ type BadgeScheme = "primary" | "secondary" | "success" | "warning" | "danger";
14
+
15
+ /** Label foreground per scheme — mirrors the spec's token table. */
16
+ const FG_TOKEN: Record<BadgeScheme, ColorToken> = {
17
+ primary: "accent.solid.fg",
18
+ secondary: "neutral.solid.fg",
19
+ success: "success.solid.fg",
20
+ warning: "warning.solid.fg",
21
+ danger: "danger.solid.fg",
22
+ };
23
+
24
+ const LABEL_VARIANT: Record<BadgeSize, RoleTextVariant> = {
25
+ sm: "label-sm",
26
+ md: "label-md",
27
+ };
28
+
29
+ export interface BadgeIndicatorProps {
30
+ label?: string | number;
31
+ color: BadgeColor;
32
+ size: BadgeSize;
33
+ accessibilityLabel?: string;
34
+ }
35
+
36
+ /**
37
+ * The overlay itself: a dot, or a pill carrying the label. Owns the paint, the
38
+ * legibility ring, and the accessibility node — the wrapper owns only position.
39
+ *
40
+ * `useUnistyles` is called unconditionally, which is what subscribes this
41
+ * component to theme changes. The background below comes from a variant, which
42
+ * unistyles applies straight to the native node on a theme switch; without a
43
+ * re-render React would go on believing the node holds the previous theme's
44
+ * value. `Chip` documents the same hazard on its root.
45
+ */
46
+ export const BadgeIndicator = ({ label, color, size, accessibilityLabel }: BadgeIndicatorProps) => {
47
+ const { theme } = useUnistyles();
48
+ // A literal never matches one of the five variant options, so it's cast to
49
+ // satisfy `useVariants`'s literal-union type while still being compared
50
+ // against the real value below.
51
+ styles.useVariants({ color: color as BadgeScheme, size });
52
+
53
+ const custom = SCHEME_NAMES.has(color) ? null : getCustomBadgeColors(color);
54
+ const labelled = hasLabel(label);
55
+ const foreground = custom ? custom.fg : FG_TOKEN[color as BadgeScheme];
56
+
57
+ // An explicit label wins even on a dot; a dot with nothing to announce is
58
+ // decorative and hidden rather than read out as an unlabelled element.
59
+ const announced = accessibilityLabel ?? (labelled ? String(label) : undefined);
60
+ const accessibility = announced
61
+ ? { accessible: true, accessibilityLabel: announced }
62
+ : {
63
+ accessibilityElementsHidden: true,
64
+ importantForAccessibility: "no-hide-descendants" as const,
65
+ "aria-hidden": true,
66
+ };
67
+
68
+ return (
69
+ <View
70
+ style={[
71
+ styles.indicator,
72
+ labelled ? styles.pill : styles.dot,
73
+ custom ? { backgroundColor: custom.bg } : null,
74
+ ]}
75
+ {...accessibility}
76
+ >
77
+ {labelled ? (
78
+ <RNText
79
+ style={[styles.label, { color: resolveThemeColor(foreground, theme) }]}
80
+ maxFontSizeMultiplier={theme.typography.variants[LABEL_VARIANT[size]].maxFontSizeMultiplier}
81
+ numberOfLines={1}
82
+ >
83
+ {label}
84
+ </RNText>
85
+ ) : null}
86
+ </View>
87
+ );
88
+ };
89
+
90
+ // ─── Styles ───────────────────────────────────────────────────────────────────
91
+ //
92
+ // `color` is a plain variant rather than a compound one: `Badge` renders solid
93
+ // only, so there is no second paint axis for it to interact with.
94
+ //
95
+ // React Native's box model is border-box, so the ring below eats into the
96
+ // indicator rather than growing it — the dot's outer diameter stays exactly
97
+ // what `size` declares, with no compensation arithmetic.
98
+ const styles = StyleSheet.create((theme) => {
99
+ const c = theme.color;
100
+ // A custom font encodes its weight in the family name, so only the system
101
+ // font takes a numeric `fontWeight` — the same branch `Chip` makes. Inlined
102
+ // rather than extracted: the unistyles Babel plugin rewrites this factory,
103
+ // and an imported function called from inside it is not in scope at runtime.
104
+ const labelFont = theme.typography.fonts.semibold;
105
+ const isSystemLabelFont = labelFont === SYSTEM_FONT;
106
+
107
+ return {
108
+ indicator: {
109
+ alignItems: "center",
110
+ justifyContent: "center",
111
+ borderRadius: theme.radius.full,
112
+ borderColor: c["surface.canvas"],
113
+ variants: {
114
+ size: {
115
+ sm: { borderWidth: 1.5 },
116
+ md: { borderWidth: 2 },
117
+ },
118
+ color: {
119
+ primary: { backgroundColor: c["accent.solid.bg"] },
120
+ secondary: { backgroundColor: c["neutral.solid.bg"] },
121
+ success: { backgroundColor: c["success.solid.bg"] },
122
+ warning: { backgroundColor: c["warning.solid.bg"] },
123
+ danger: { backgroundColor: c["danger.solid.bg"] },
124
+ },
125
+ },
126
+ },
127
+ dot: {
128
+ variants: {
129
+ size: {
130
+ sm: { width: 10, height: 10 },
131
+ md: { width: 12, height: 12 },
132
+ },
133
+ },
134
+ },
135
+ pill: {
136
+ variants: {
137
+ size: {
138
+ sm: { height: 16, minWidth: 16, paddingHorizontal: 4 },
139
+ md: { height: 20, minWidth: 20, paddingHorizontal: 6 },
140
+ },
141
+ },
142
+ },
143
+ // Spelled out rather than derived from `LABEL_VARIANT`: the unistyles Babel
144
+ // plugin reads this object statically, so computed variant keys are not
145
+ // safe here even though they would resolve correctly under test.
146
+ label: {
147
+ textAlign: "center",
148
+ fontFamily: isSystemLabelFont ? undefined : labelFont,
149
+ fontWeight: isSystemLabelFont ? FONT_WEIGHT_VALUE.semibold : undefined,
150
+ variants: {
151
+ size: {
152
+ sm: {
153
+ fontSize: theme.typography.variants["label-sm"].fontSize,
154
+ lineHeight: theme.typography.variants["label-sm"].lineHeight,
155
+ letterSpacing: theme.typography.variants["label-sm"].letterSpacing,
156
+ },
157
+ md: {
158
+ fontSize: theme.typography.variants["label-md"].fontSize,
159
+ lineHeight: theme.typography.variants["label-md"].lineHeight,
160
+ letterSpacing: theme.typography.variants["label-md"].letterSpacing,
161
+ },
162
+ },
163
+ },
164
+ },
165
+ };
166
+ });
@@ -0,0 +1,58 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ViewProps } from "react-native";
3
+
4
+ /** Corner of the wrapped child the indicator is anchored to. */
5
+ export type BadgePlacement = "top-right" | "top-left" | "bottom-right" | "bottom-left";
6
+
7
+ /** Scale of the dot, pill, and label. */
8
+ export type BadgeSize = "sm" | "md";
9
+
10
+ /**
11
+ * Semantic color scheme, or a raw CSS color literal (`#fff`, `rgb(...)`, `hsl(...)`).
12
+ * A literal paints the indicator with that exact value and derives a readable
13
+ * foreground for the label.
14
+ */
15
+ export type BadgeColor =
16
+ | "primary"
17
+ | "secondary"
18
+ | "success"
19
+ | "warning"
20
+ | "danger"
21
+ // literals last so scheme-name autocomplete still surfaces
22
+ | (string & {});
23
+
24
+ export interface BadgeProps extends ViewProps {
25
+ /** The element being badged. Rendered untouched; `Badge` only adds the overlay. */
26
+ children: ReactNode;
27
+ /**
28
+ * Corner the indicator sits on. Defaults to `'top-right'`.
29
+ *
30
+ * The indicator overhangs the child, so an ancestor that clips — a `Surface`
31
+ * with a radius, or a `Screen` list row — will cut it off. Pad that container
32
+ * by the overhang, or lift the `Badge` outside it.
33
+ */
34
+ placement?: BadgePlacement;
35
+ /**
36
+ * Text in the indicator. Omit it (or pass an empty string) for a dot.
37
+ * `0` renders as a label — use `isVisible` to suppress a zero count.
38
+ */
39
+ label?: string | number;
40
+ /** Semantic scheme or raw color literal. Defaults to `'danger'`. */
41
+ color?: BadgeColor;
42
+ /** Scale of the indicator. Defaults to `'md'`. */
43
+ size?: BadgeSize;
44
+ /** Renders the indicator. The child renders either way. Defaults to `true`. */
45
+ isVisible?: boolean;
46
+ /**
47
+ * Stretches the wrapper along its parent's cross axis, so a child that
48
+ * stretches unwrapped keeps stretching. Defaults to `false`, which hugs the
49
+ * child — what an icon, avatar, or toolbar button needs. An explicit
50
+ * `alignSelf` in `style` beats both.
51
+ */
52
+ fullWidth?: boolean;
53
+ /**
54
+ * Announced in place of the label. Defaults to the label's own text; an
55
+ * unlabelled dot is hidden from assistive technology.
56
+ */
57
+ accessibilityLabel?: string;
58
+ }
@@ -0,0 +1,24 @@
1
+ import { readableOn } from "#utils/color-utils";
2
+
3
+ // A custom color literal (hex/rgb/hsl) isn't a bounded set of options, so it
4
+ // can't be a unistyles variant condition — it's derived here instead, the same
5
+ // way `chip/utils.ts` handles the equivalent case on `Chip`.
6
+
7
+ export interface CustomBadgeColors {
8
+ bg: string;
9
+ fg: string;
10
+ }
11
+
12
+ /** The indicator's paint for a raw literal: the literal itself, plus a readable label. */
13
+ export function getCustomBadgeColors(literal: string): CustomBadgeColors {
14
+ return { bg: literal, fg: readableOn(literal) };
15
+ }
16
+
17
+ /**
18
+ * `true` when `label` should render as a pill. An empty string and `undefined`
19
+ * are a dot; `0` is a label, since a caller suppressing a zero count uses
20
+ * `isVisible` rather than relying on falsiness.
21
+ */
22
+ export function hasLabel(label: string | number | undefined): label is string | number {
23
+ return label !== undefined && label !== "";
24
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./avatar";
2
+ export * from "./badge";
2
3
  export * from "./box";
3
4
  export * from "./button";
4
5
  export * from "./chip";