harebear-ui 0.8.0 → 0.8.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.
@@ -4,6 +4,7 @@ import { Badge } from "../ui/badge.js";
4
4
  import { Button } from "../ui/button.js";
5
5
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js";
6
6
  import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar.js";
7
+ import { useToastClearance } from "../../lib/toast-clearance.js";
7
8
  import { cn } from "../../lib/utils.js";
8
9
  import * as React from "react";
9
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -18,6 +19,8 @@ const TAB_LIMIT = 5;
18
19
  */
19
20
  function AppNav({ brand, places, group, footer, action, label = "Main", linkComponent = "a", collapsible, storageKey, defaultOpen, width = "15.75rem", className, children }) {
20
21
  const tabs = places.filter((p) => p.tab !== false).slice(0, TAB_LIMIT);
22
+ useToastClearance("--hb-toast-nav", "calc(4.5rem + env(safe-area-inset-bottom))", tabs.length > 0);
23
+ useToastClearance("--hb-toast-fab", "4.5rem", Boolean(action));
21
24
  return /* @__PURE__ */ jsxs(SidebarLayout, {
22
25
  brand,
23
26
  collapsible,
@@ -2,6 +2,7 @@
2
2
  import { AskBox } from "./ask-box.js";
3
3
  import { AssistantAvatar, ChatMessage, ChatThread, TypingIndicator } from "./chat.js";
4
4
  import { Button } from "../ui/button.js";
5
+ import { useToastClearance } from "../../lib/toast-clearance.js";
5
6
  import { cn } from "../../lib/utils.js";
6
7
  import * as React from "react";
7
8
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -136,6 +137,7 @@ function AssistantDock({ title = "Assistant", icon = /* @__PURE__ */ jsx(Sparkle
136
137
  panelRef.current?.querySelector("textarea")?.focus();
137
138
  send(text);
138
139
  };
140
+ useToastClearance("--hb-toast-launcher", "3.5rem", !open && launcher);
139
141
  if (!open) {
140
142
  if (!launcher) return null;
141
143
  return /* @__PURE__ */ jsxs("button", {
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ // src/lib/toast-clearance.ts
4
+ const counts = /* @__PURE__ */ new Map();
5
+ function useToastClearance(name, value, enabled = true) {
6
+ React.useEffect(() => {
7
+ if (!enabled || typeof document === "undefined") return;
8
+ const root = document.documentElement;
9
+ counts.set(name, (counts.get(name) ?? 0) + 1);
10
+ root.style.setProperty(name, value);
11
+ return () => {
12
+ const left = (counts.get(name) ?? 1) - 1;
13
+ counts.set(name, left);
14
+ if (left === 0) root.style.removeProperty(name);
15
+ };
16
+ }, [
17
+ name,
18
+ value,
19
+ enabled
20
+ ]);
21
+ }
22
+ export { useToastClearance };
package/dist/styles.css CHANGED
@@ -285,3 +285,26 @@
285
285
  background-color: color-mix(in oklab, var(--muted-foreground) 60%, transparent);
286
286
  }
287
287
  }
288
+
289
+ /* Toasts clear floating controls. AppNav (tab bar, action button) and AssistantDock (launcher) set --hb-toast-* on
290
+ <html> while mounted (src/lib/toast-clearance.ts); this adds them to Sonner's bottom offset. Unlayered and
291
+ !important because Sonner injects its own unlayered rule at runtime. Breakpoints: AppNav's tab bar shows under
292
+ 768px; Sonner switches to full-width mobile toasts under 600px. */
293
+ [data-sonner-toaster][data-y-position="bottom"] {
294
+ bottom: calc(var(--offset-bottom) + var(--hb-toast-launcher, 0px)) !important;
295
+ }
296
+ @media (max-width: 767px) {
297
+ [data-sonner-toaster][data-y-position="bottom"] {
298
+ bottom: calc(
299
+ var(--offset-bottom) + var(--hb-toast-nav, 0px) + var(--hb-toast-fab, 0px) + var(--hb-toast-launcher, 0px)
300
+ ) !important;
301
+ }
302
+ }
303
+ @media (max-width: 600px) {
304
+ [data-sonner-toaster][data-y-position="bottom"] {
305
+ bottom: calc(
306
+ var(--mobile-offset-bottom) + var(--hb-toast-nav, 0px) + var(--hb-toast-fab, 0px) +
307
+ var(--hb-toast-launcher, 0px)
308
+ ) !important;
309
+ }
310
+ }
@@ -278,7 +278,7 @@ Muted tile that lifts on hover, for service launchers. Props: `title`, `subtitle
278
278
  One nav config, two renderings: on desktop a `SidebarLayout` sidebar (brand, action button, places, one collapsible group, footer; active place is a raised card; collapses to the icon rail), under 768px a bottom tab bar (first 5 places, 56px targets, `aria-current="page"`) plus a round floating action button. Use it instead of `AppShell` for consumer apps that need phone navigation.
279
279
 
280
280
  Props: `places: AppNavPlace[]` (`label`, `icon?`, `href?` or `onSelect?`, `active?`, `count?`, `tab?: false` to keep out of the tab bar), `group?: AppNavGroup` (`label`, `items`, `defaultOpen?`, `onAdd?`, `addLabel?`), `action?: { label; icon; onSelect }`, `brand?`, `footer?` (usually `UserMenu`), `label?` (nav name, default "Main"), `linkComponent?`, `collapsible?`, `storageKey?`, `defaultOpen?`, `width?`, `children`.
281
- On phones the group and footer live in the sidebar sheet: put a `SidebarTrigger` in the page header if users need them there. Render `AssistantDock` inside `children` so it clears the tab bar, and mount `<Toaster mobileOffset={{ bottom: "5.5rem" }} />` so toasts do too.
281
+ On phones the group and footer live in the sidebar sheet: put a `SidebarTrigger` in the page header if users need them there. Render `AssistantDock` inside `children` so it clears the tab bar. Toasts stack above the tab bar, the action button and the dock launcher on their own; no `Toaster` offset needed.
282
282
 
283
283
  ### `FocusLayout`
284
284
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harebear-ui",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Opinionated, pre-themed shadcn/ui component library for React 19 + Tailwind CSS v4. Admin, data, and AI chat building blocks, ready for agents.",
5
5
  "license": "MIT",
6
6
  "author": "Harrison Milbradt",