@tinybigui/react 0.7.0 → 0.8.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 CHANGED
@@ -12,7 +12,7 @@ A modern, accessible React component library implementing Google's Material Desi
12
12
 
13
13
  ## ✅ Status
14
14
 
15
- > **Latest Release: v0.7.0** (2026-06-09)
15
+ > **Latest Release: v0.8.0** (2026-06-09)
16
16
  >
17
17
  > **29 MD3 components** published to npm with full TypeScript support and WCAG 2.1 AA accessibility.
18
18
  >
@@ -170,15 +170,15 @@ See [THEMING.md](./THEMING.md) for the full customization guide.
170
170
 
171
171
  ### Phase 4: Data Display ✅
172
172
 
173
- | Component | Status | Description |
174
- | ------------ | ------ | --------------------------------------- |
175
- | `Card` | ✅ | Elevated, filled, outlined variants |
176
- | `List` | ✅ | Static and interactive list items |
177
- | `Chip` | ✅ | Assist, Filter, Input, Suggestion types |
178
- | `Badge` | ✅ | Dot and count notification badges |
179
- | `Divider` | ✅ | Horizontal/vertical, inset variants |
180
- | `DatePicker` | ✅ | Docked, modal, and input variants |
181
- | `TimePicker` | ✅ | 12h/24h clock dial, range selection |
173
+ | Component | Status | Description |
174
+ | ------------ | ------ | --------------------------------------------------------------- |
175
+ | `Card` | ✅ | Elevated, filled, outlined variants |
176
+ | `List` | ✅ | Static and interactive list items |
177
+ | `Chip` | ✅ | Assist, Filter, Input, Suggestion types |
178
+ | `Badge` | ✅ | MD3 expressive dot/count badges, icon-corner anchoring (v0.8.0) |
179
+ | `Divider` | ✅ | Horizontal/vertical, inset variants |
180
+ | `DatePicker` | ✅ | Docked, modal, and input variants |
181
+ | `TimePicker` | ✅ | 12h/24h clock dial, range selection |
182
182
 
183
183
  ### Planned
184
184
 
package/dist/index.cjs CHANGED
@@ -5070,106 +5070,48 @@ var Drawer = React.forwardRef(
5070
5070
  }
5071
5071
  );
5072
5072
  Drawer.displayName = "Drawer";
5073
- var BadgeHeadless = React.forwardRef(
5074
- ({ className, children, ...props }, ref) => {
5075
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("relative inline-flex", className), ...props, children });
5076
- }
5077
- );
5078
- BadgeHeadless.displayName = "BadgeHeadless";
5079
- var badgeVariants2 = classVarianceAuthority.cva(
5080
- ["absolute -top-1 -right-1 rounded-full flex items-center justify-center"],
5081
- {
5082
- variants: {
5083
- size: {
5084
- small: "size-1.5",
5085
- large: "min-w-4 h-4 px-1 text-label-small"
5086
- },
5087
- color: {
5088
- error: "bg-error text-on-error",
5089
- primary: "bg-primary text-on-primary"
5090
- },
5091
- invisible: {
5092
- true: "scale-0 opacity-0",
5093
- false: "scale-100 opacity-100"
5094
- },
5095
- reducedMotion: {
5096
- true: "",
5097
- false: "transition-[transform,opacity] duration-spring-standard-fast-effects ease-spring-standard-fast-effects"
5098
- }
5099
- },
5100
- defaultVariants: {
5101
- size: "large",
5102
- color: "error",
5103
- invisible: false,
5104
- reducedMotion: false
5105
- }
5106
- }
5107
- );
5108
- var getDisplayValue = (count, max) => {
5073
+ var badgeAppearance = [
5074
+ // ── Shape ─────────────────────────────────────────────────────────────────────
5075
+ "flex items-center justify-center",
5076
+ "rounded-full",
5077
+ // ── Large (count) sizing — base defaults ──────────────────────────────────────
5078
+ // Height 16dp, min-width 16dp, horizontal padding 4dp
5079
+ "h-4 min-w-4 px-1",
5080
+ // ── Color error role (only MD3-spec role for badges) ────────────────────────
5081
+ "bg-error text-on-error",
5082
+ // ── Typography — label-small, tight leading, tabular numbers ──────────────────
5083
+ "text-label-small leading-none tabular-nums",
5084
+ // ── Visibility (runtime flag) ──────────────────────────────────────────────────
5085
+ // Base: fully visible
5086
+ "scale-100",
5087
+ // data-invisible: scale to zero (visually hidden; aria-label still readable by SR)
5088
+ "data-[invisible]:scale-0",
5089
+ // ── Dot content flag overrides (placed last — cascade wins over base sizing) ───
5090
+ // Clear out the count-pill sizing, set 6dp circle
5091
+ "data-[dot]:size-1.5",
5092
+ "data-[dot]:min-w-0",
5093
+ "data-[dot]:p-0",
5094
+ "data-[dot]:text-[0]"
5095
+ // suppress any stray text rendering on dot
5096
+ ];
5097
+ var badgeVariants2 = classVarianceAuthority.cva([
5098
+ // ── Anchored placement — badge center on host's top-right corner ──────────────
5099
+ // top-0 right-0 places the badge's own top-right at the host's top-right,
5100
+ // then the 1/2-element translate moves the badge center onto that corner.
5101
+ // Host-size-agnostic: works for any wrapped element (icon, avatar, nav chip).
5102
+ "absolute top-0 right-0 -translate-y-1/2 translate-x-1/2",
5103
+ ...badgeAppearance
5104
+ ]);
5105
+ var badgeStaticVariants = classVarianceAuthority.cva(["inline-flex", ...badgeAppearance]);
5106
+ function isBadgeConfig(badge) {
5107
+ return typeof badge === "object" && badge !== null && !React.isValidElement(badge) && "count" in badge;
5108
+ }
5109
+ function getBadgeDisplayValue(count, max) {
5109
5110
  if (count === void 0) return "";
5110
5111
  return count > max ? `${max}+` : count.toString();
5111
- };
5112
- var getAriaLabel = (count, override) => {
5113
- if (override) return override;
5112
+ }
5113
+ function getBadgeAriaLabel(count) {
5114
5114
  return count === void 0 ? "New" : `${count} notifications`;
5115
- };
5116
- var BadgeContent = React.forwardRef(
5117
- ({
5118
- count,
5119
- max = 999,
5120
- color = "error",
5121
- invisible = false,
5122
- "aria-label": ariaLabelOverride,
5123
- reducedMotion = false,
5124
- className
5125
- }, ref) => {
5126
- const size = count === void 0 ? "small" : "large";
5127
- const displayValue = getDisplayValue(count, max);
5128
- const ariaLabel = getAriaLabel(count, ariaLabelOverride);
5129
- return /* @__PURE__ */ jsxRuntime.jsx(
5130
- "span",
5131
- {
5132
- ref,
5133
- role: "status",
5134
- "aria-label": ariaLabel,
5135
- className: cn(badgeVariants2({ size, color, invisible, reducedMotion }), className),
5136
- children: displayValue
5137
- }
5138
- );
5139
- }
5140
- );
5141
- BadgeContent.displayName = "BadgeContent";
5142
- var Badge = React.forwardRef(
5143
- ({
5144
- count,
5145
- max = 999,
5146
- color = "error",
5147
- invisible = false,
5148
- "aria-label": ariaLabel,
5149
- className,
5150
- children
5151
- }, ref) => {
5152
- const isReduced = useReducedMotion();
5153
- const shouldShow = !invisible && (count === void 0 || count > 0);
5154
- return /* @__PURE__ */ jsxRuntime.jsxs(BadgeHeadless, { ref, className, children: [
5155
- children,
5156
- /* @__PURE__ */ jsxRuntime.jsx(
5157
- BadgeContent,
5158
- {
5159
- count,
5160
- max,
5161
- color,
5162
- invisible: !shouldShow,
5163
- "aria-label": ariaLabel,
5164
- reducedMotion: isReduced
5165
- }
5166
- )
5167
- ] });
5168
- }
5169
- );
5170
- Badge.displayName = "Badge";
5171
- function isBadgeConfig(badge) {
5172
- return typeof badge === "object" && badge !== null && !React.isValidElement(badge) && ("count" in badge || "color" in badge);
5173
5115
  }
5174
5116
  var DrawerItem = React.forwardRef(
5175
5117
  ({
@@ -5197,12 +5139,18 @@ var DrawerItem = React.forwardRef(
5197
5139
  const renderBadge = () => {
5198
5140
  if (!badge) return null;
5199
5141
  if (isBadgeConfig(badge)) {
5142
+ const max = 999;
5143
+ const isDot = badge.count === void 0;
5144
+ const displayValue = getBadgeDisplayValue(badge.count, max);
5145
+ const ariaLabel = getBadgeAriaLabel(badge.count);
5200
5146
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative z-10 ml-auto flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsxRuntime.jsx(
5201
- Badge,
5147
+ "span",
5202
5148
  {
5203
- ...badge.count !== void 0 ? { count: badge.count } : {},
5204
- ...badge.color !== void 0 ? { color: badge.color } : {},
5205
- children: /* @__PURE__ */ jsxRuntime.jsx("span", {})
5149
+ role: "status",
5150
+ "aria-label": ariaLabel,
5151
+ "data-dot": isDot ? "" : void 0,
5152
+ className: cn(badgeStaticVariants()),
5153
+ children: displayValue
5206
5154
  }
5207
5155
  ) });
5208
5156
  }
@@ -8701,6 +8649,75 @@ var Search = React.forwardRef(
8701
8649
  }
8702
8650
  );
8703
8651
  Search.displayName = "Search";
8652
+ var BadgeHeadless = React.forwardRef(
8653
+ ({ className, children, ...props }, ref) => {
8654
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("relative inline-flex", className), ...props, children });
8655
+ }
8656
+ );
8657
+ BadgeHeadless.displayName = "BadgeHeadless";
8658
+ var getDisplayValue = (count, max) => {
8659
+ if (count === void 0) return "";
8660
+ return count > max ? `${max}+` : count.toString();
8661
+ };
8662
+ var getAriaLabel = (count, override) => {
8663
+ if (override) return override;
8664
+ return count === void 0 ? "New" : `${count} notifications`;
8665
+ };
8666
+ var BadgeContent = React.forwardRef(
8667
+ ({
8668
+ count,
8669
+ max = 999,
8670
+ invisible = false,
8671
+ "aria-label": ariaLabelOverride,
8672
+ reducedMotion = false,
8673
+ className
8674
+ }, ref) => {
8675
+ const isDot = count === void 0;
8676
+ const displayValue = getDisplayValue(count, max);
8677
+ const ariaLabel = getAriaLabel(count, ariaLabelOverride);
8678
+ return /* @__PURE__ */ jsxRuntime.jsx(
8679
+ "span",
8680
+ {
8681
+ ref,
8682
+ role: "status",
8683
+ "aria-label": ariaLabel,
8684
+ "data-dot": isDot ? "" : void 0,
8685
+ "data-invisible": invisible ? "" : void 0,
8686
+ className: cn(
8687
+ badgeVariants2(),
8688
+ // MD3 Expressive spatial motion for show/hide scale animation.
8689
+ // Spatial pairing: scale transform → expressive-fast-spatial token.
8690
+ // Guarded at the component level; do NOT use CSS-only reduced-motion
8691
+ // because this is a JS-conditional class, not a persistent transition.
8692
+ !reducedMotion && "duration-expressive-fast-spatial ease-expressive-fast-spatial transition-transform",
8693
+ className
8694
+ ),
8695
+ children: displayValue
8696
+ }
8697
+ );
8698
+ }
8699
+ );
8700
+ BadgeContent.displayName = "BadgeContent";
8701
+ var Badge = React.forwardRef(
8702
+ ({ count, max = 999, invisible = false, "aria-label": ariaLabel, className, children }, ref) => {
8703
+ const isReduced = useReducedMotion();
8704
+ const shouldShow = !invisible && (count === void 0 || count > 0);
8705
+ return /* @__PURE__ */ jsxRuntime.jsxs(BadgeHeadless, { ref, className, children: [
8706
+ children,
8707
+ /* @__PURE__ */ jsxRuntime.jsx(
8708
+ BadgeContent,
8709
+ {
8710
+ count,
8711
+ max,
8712
+ invisible: !shouldShow,
8713
+ "aria-label": ariaLabel,
8714
+ reducedMotion: isReduced
8715
+ }
8716
+ )
8717
+ ] });
8718
+ }
8719
+ );
8720
+ Badge.displayName = "Badge";
8704
8721
  var splitButtonContainerVariants = classVarianceAuthority.cva(
8705
8722
  ["inline-flex items-center rounded-full overflow-hidden"],
8706
8723
  {