@epicbagel/saas-kit 1.18.0 → 1.19.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
@@ -15,6 +15,15 @@ npm install @epicbagel/saas-kit
15
15
  Peer dependencies: `react` and `react-dom` (18 or 19), `lucide-react`
16
16
  (`>=0.400.0 <2`), `react-icons` (5) and `tailwindcss` (4.3 or later).
17
17
 
18
+ ## Frosted scroll navigation
19
+
20
+ `ScrollNavigator frosted` gives its floating controls a translucent themed
21
+ background with 16px backdrop blur. `MessageScrollerNavigator` forwards the same
22
+ option. The default appearance, list layout and navigation behavior are unchanged.
23
+ The surface stays opaque when backdrop blur is unsupported, reduced transparency
24
+ is requested, or forced colours are active. Import the kit's `globals.css` (or
25
+ `tokens.css` and `utilities.css`) as usual.
26
+
18
27
  ## Page header actions
19
28
 
20
29
  `PageHeaderActions` expands its actions on desktop and collapses them into
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
2
2
 
3
3
 
4
- var _chunk6AMSRIQ4cjs = require('./chunk-6AMSRIQ4.cjs');
4
+ var _chunkDMPSKGFMcjs = require('./chunk-DMPSKGFM.cjs');
5
5
 
6
6
 
7
7
  var _chunkJDEXZGIYcjs = require('./chunk-JDEXZGIY.cjs');
@@ -23,7 +23,7 @@ function MessageScrollerProvider(props) {
23
23
  }
24
24
  function MessageScrollerNavigator(props) {
25
25
  const { scrollToMessage } = _messagescroller.useMessageScroller.call(void 0, );
26
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk6AMSRIQ4cjs.ScrollNavigator, { ...props, onNavigate: (id, target, options) => {
26
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDMPSKGFMcjs.ScrollNavigator, { ...props, onNavigate: (id, target, options) => {
27
27
  if (_optionalChain([props, 'access', _ => _.onNavigate, 'optionalCall', _2 => _2(id, target, options)]) === true) return true;
28
28
  const message = target.closest("[data-message-id]");
29
29
  const messageId = _optionalChain([message, 'optionalAccess', _3 => _3.dataset, 'access', _4 => _4.messageId]);
@@ -58,6 +58,7 @@ var ScrollNavigator = forwardRef(function ScrollNavigator2({
58
58
  scrollRoot,
59
59
  onNavigate,
60
60
  className,
61
+ frosted = false,
61
62
  ...props
62
63
  }, forwardedRef) {
63
64
  const rootRef = useRef(null);
@@ -195,7 +196,8 @@ var ScrollNavigator = forwardRef(function ScrollNavigator2({
195
196
  "aria-label": label,
196
197
  "data-slot": "scroll-navigator-controls",
197
198
  "data-orientation": row ? "horizontal" : "vertical",
198
- className: cn("fixed z-20 flex rounded-full border border-border/60 bg-background/95 p-1 shadow-sm backdrop-blur-sm", row ? "items-center gap-1" : "flex-col items-center gap-1"),
199
+ "data-frosted-surface": frosted ? "background" : void 0,
200
+ className: cn("fixed z-20 flex rounded-full border border-border/60 p-1 shadow-sm", !frosted && "bg-background/95 backdrop-blur-sm", row ? "items-center gap-1" : "flex-col items-center gap-1"),
199
201
  style: { top: placement.top, left: placement.left, width: placement.width, maxHeight: placement.maxHeight },
200
202
  onBlur: (event) => {
201
203
  if (!event.currentTarget.contains(event.relatedTarget)) setFocusId(null);
@@ -249,7 +251,7 @@ var ScrollNavigator = forwardRef(function ScrollNavigator2({
249
251
  ))
250
252
  }
251
253
  ),
252
- /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: cn("shrink-0 text-center text-[10px] tabular-nums text-muted-foreground", row ? "px-2" : "pb-1"), children: [
254
+ /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: cn("shrink-0 text-center text-[10px] tabular-nums", frosted ? "text-foreground" : "text-muted-foreground", row ? "px-2" : "pb-1"), children: [
253
255
  placement.active + 1,
254
256
  " / ",
255
257
  items.length
@@ -58,6 +58,7 @@ var ScrollNavigator = _react.forwardRef.call(void 0, function ScrollNavigator2({
58
58
  scrollRoot,
59
59
  onNavigate,
60
60
  className,
61
+ frosted = false,
61
62
  ...props
62
63
  }, forwardedRef) {
63
64
  const rootRef = _react.useRef.call(void 0, null);
@@ -195,7 +196,8 @@ var ScrollNavigator = _react.forwardRef.call(void 0, function ScrollNavigator2({
195
196
  "aria-label": label,
196
197
  "data-slot": "scroll-navigator-controls",
197
198
  "data-orientation": row ? "horizontal" : "vertical",
198
- className: _chunkKWLSBTB2cjs.cn.call(void 0, "fixed z-20 flex rounded-full border border-border/60 bg-background/95 p-1 shadow-sm backdrop-blur-sm", row ? "items-center gap-1" : "flex-col items-center gap-1"),
199
+ "data-frosted-surface": frosted ? "background" : void 0,
200
+ className: _chunkKWLSBTB2cjs.cn.call(void 0, "fixed z-20 flex rounded-full border border-border/60 p-1 shadow-sm", !frosted && "bg-background/95 backdrop-blur-sm", row ? "items-center gap-1" : "flex-col items-center gap-1"),
199
201
  style: { top: placement.top, left: placement.left, width: placement.width, maxHeight: placement.maxHeight },
200
202
  onBlur: (event) => {
201
203
  if (!event.currentTarget.contains(event.relatedTarget)) setFocusId(null);
@@ -249,7 +251,7 @@ var ScrollNavigator = _react.forwardRef.call(void 0, function ScrollNavigator2({
249
251
  ))
250
252
  }
251
253
  ),
252
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { "aria-hidden": "true", className: _chunkKWLSBTB2cjs.cn.call(void 0, "shrink-0 text-center text-[10px] tabular-nums text-muted-foreground", row ? "px-2" : "pb-1"), children: [
254
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { "aria-hidden": "true", className: _chunkKWLSBTB2cjs.cn.call(void 0, "shrink-0 text-center text-[10px] tabular-nums", frosted ? "text-foreground" : "text-muted-foreground", row ? "px-2" : "pb-1"), children: [
253
255
  placement.active + 1,
254
256
  " / ",
255
257
  items.length
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  ScrollNavigator
4
- } from "./chunk-V3XA7SDK.js";
4
+ } from "./chunk-36LNA4WM.js";
5
5
  import {
6
6
  Button
7
7
  } from "./chunk-W326GKML.js";
@@ -26,8 +26,8 @@ var _chunkB765DSRGcjs = require('../chunk-B765DSRG.cjs');
26
26
 
27
27
 
28
28
 
29
- var _chunkHSSSNAVXcjs = require('../chunk-HSSSNAVX.cjs');
30
- require('../chunk-6AMSRIQ4.cjs');
29
+ var _chunk23IETCLVcjs = require('../chunk-23IETCLV.cjs');
30
+ require('../chunk-DMPSKGFM.cjs');
31
31
 
32
32
 
33
33
 
@@ -179,15 +179,15 @@ var ChatThread = _react.forwardRef.call(void 0,
179
179
  const markers = timelineMarkers(messages, /* @__PURE__ */ new Date());
180
180
  const anchorId = useArrivingUserTurnId(messages);
181
181
  const workspace = appearance === "workspace";
182
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkHSSSNAVXcjs.MessageScrollerProvider, { autoScroll: true, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
183
- _chunkHSSSNAVXcjs.MessageScroller,
182
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk23IETCLVcjs.MessageScrollerProvider, { autoScroll: true, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
183
+ _chunk23IETCLVcjs.MessageScroller,
184
184
  {
185
185
  ref,
186
186
  style: { maxHeight },
187
187
  className: _chunkKWLSBTB2cjs.cn.call(void 0, "min-h-0", className),
188
188
  children: [
189
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkHSSSNAVXcjs.MessageScrollerViewport, { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
190
- _chunkHSSSNAVXcjs.MessageScrollerContent,
189
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk23IETCLVcjs.MessageScrollerViewport, { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
190
+ _chunk23IETCLVcjs.MessageScrollerContent,
191
191
  {
192
192
  className: _chunkKWLSBTB2cjs.cn.call(void 0,
193
193
  workspace ? "mx-auto w-full max-w-3xl gap-10 px-4 py-6 md:px-6 md:py-8" : "gap-2.5 px-px py-px"
@@ -196,7 +196,7 @@ var ChatThread = _react.forwardRef.call(void 0,
196
196
  messages.map((m, index) => {
197
197
  const isUser = m.role === "user";
198
198
  if (m.status === "running") {
199
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkHSSSNAVXcjs.MessageScrollerItem, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
199
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk23IETCLVcjs.MessageScrollerItem, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
200
200
  ChatRunningLine,
201
201
  {
202
202
  label: m.text || pendingLabel,
@@ -209,7 +209,7 @@ var ChatThread = _react.forwardRef.call(void 0,
209
209
  const marker = markers[index];
210
210
  const actions = messageActions && offersActions(m) ? messageActions(m) : null;
211
211
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
212
- _chunkHSSSNAVXcjs.MessageScrollerItem,
212
+ _chunk23IETCLVcjs.MessageScrollerItem,
213
213
  {
214
214
  messageId: m.id,
215
215
  scrollAnchor: m.id === anchorId,
@@ -265,15 +265,15 @@ var ChatThread = _react.forwardRef.call(void 0,
265
265
  m.id
266
266
  );
267
267
  }),
268
- pending && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkHSSSNAVXcjs.MessageScrollerItem, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ChatRunningLine, { label: pendingLabel, workspace }) }),
268
+ pending && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk23IETCLVcjs.MessageScrollerItem, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ChatRunningLine, { label: pendingLabel, workspace }) }),
269
269
  footer && // Always painted: content-visibility:auto would contain the
270
270
  // footer's paint and layout, clipping a ring or shadow at its
271
271
  // edge and re-parenting anything fixed inside it.
272
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkHSSSNAVXcjs.MessageScrollerItem, { className: "[content-visibility:visible]", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkNWDPH2Z7cjs.Message, { align: "start", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkNWDPH2Z7cjs.MessageContent, { children: footer }) }) })
272
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk23IETCLVcjs.MessageScrollerItem, { className: "[content-visibility:visible]", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkNWDPH2Z7cjs.Message, { align: "start", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkNWDPH2Z7cjs.MessageContent, { children: footer }) }) })
273
273
  ]
274
274
  }
275
275
  ) }),
276
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkHSSSNAVXcjs.MessageScrollerButton, { variant: "outline", className: "rounded-full shadow-sm", children: [
276
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk23IETCLVcjs.MessageScrollerButton, { variant: "outline", className: "rounded-full shadow-sm", children: [
277
277
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.ArrowDown, { className: "h-4 w-4" }),
278
278
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "sr-only", children: scrollButtonLabel })
279
279
  ] })
@@ -26,8 +26,8 @@ import {
26
26
  MessageScrollerItem,
27
27
  MessageScrollerProvider,
28
28
  MessageScrollerViewport
29
- } from "../chunk-EXXJXOUO.js";
30
- import "../chunk-V3XA7SDK.js";
29
+ } from "../chunk-OUZSIA5G.js";
30
+ import "../chunk-36LNA4WM.js";
31
31
  import {
32
32
  Message,
33
33
  MessageAction,
@@ -11,8 +11,8 @@
11
11
 
12
12
 
13
13
 
14
- var _chunkHSSSNAVXcjs = require('../chunk-HSSSNAVX.cjs');
15
- require('../chunk-6AMSRIQ4.cjs');
14
+ var _chunk23IETCLVcjs = require('../chunk-23IETCLV.cjs');
15
+ require('../chunk-DMPSKGFM.cjs');
16
16
  require('../chunk-Z2H4LJVG.cjs');
17
17
  require('../chunk-WABCLRBR.cjs');
18
18
  require('../chunk-JDEXZGIY.cjs');
@@ -28,4 +28,4 @@ require('../chunk-KWLSBTB2.cjs');
28
28
 
29
29
 
30
30
 
31
- exports.MessageScroller = _chunkHSSSNAVXcjs.MessageScroller; exports.MessageScrollerButton = _chunkHSSSNAVXcjs.MessageScrollerButton; exports.MessageScrollerContent = _chunkHSSSNAVXcjs.MessageScrollerContent; exports.MessageScrollerItem = _chunkHSSSNAVXcjs.MessageScrollerItem; exports.MessageScrollerNavigator = _chunkHSSSNAVXcjs.MessageScrollerNavigator; exports.MessageScrollerProvider = _chunkHSSSNAVXcjs.MessageScrollerProvider; exports.MessageScrollerViewport = _chunkHSSSNAVXcjs.MessageScrollerViewport; exports.useMessageScroller = _chunkHSSSNAVXcjs.useMessageScroller; exports.useMessageScrollerScrollable = _chunkHSSSNAVXcjs.useMessageScrollerScrollable; exports.useMessageScrollerVisibility = _chunkHSSSNAVXcjs.useMessageScrollerVisibility;
31
+ exports.MessageScroller = _chunk23IETCLVcjs.MessageScroller; exports.MessageScrollerButton = _chunk23IETCLVcjs.MessageScrollerButton; exports.MessageScrollerContent = _chunk23IETCLVcjs.MessageScrollerContent; exports.MessageScrollerItem = _chunk23IETCLVcjs.MessageScrollerItem; exports.MessageScrollerNavigator = _chunk23IETCLVcjs.MessageScrollerNavigator; exports.MessageScrollerProvider = _chunk23IETCLVcjs.MessageScrollerProvider; exports.MessageScrollerViewport = _chunk23IETCLVcjs.MessageScrollerViewport; exports.useMessageScroller = _chunk23IETCLVcjs.useMessageScroller; exports.useMessageScrollerScrollable = _chunk23IETCLVcjs.useMessageScrollerScrollable; exports.useMessageScrollerVisibility = _chunk23IETCLVcjs.useMessageScrollerVisibility;
@@ -11,8 +11,8 @@ import {
11
11
  useMessageScroller,
12
12
  useMessageScrollerScrollable,
13
13
  useMessageScrollerVisibility
14
- } from "../chunk-EXXJXOUO.js";
15
- import "../chunk-V3XA7SDK.js";
14
+ } from "../chunk-OUZSIA5G.js";
15
+ import "../chunk-36LNA4WM.js";
16
16
  import "../chunk-GA5SX7FE.js";
17
17
  import "../chunk-3OWGL4Q2.js";
18
18
  import "../chunk-W326GKML.js";
@@ -2,10 +2,10 @@
2
2
  "use client";
3
3
 
4
4
 
5
- var _chunk6AMSRIQ4cjs = require('../chunk-6AMSRIQ4.cjs');
5
+ var _chunkDMPSKGFMcjs = require('../chunk-DMPSKGFM.cjs');
6
6
  require('../chunk-Z2H4LJVG.cjs');
7
7
  require('../chunk-WABCLRBR.cjs');
8
8
  require('../chunk-KWLSBTB2.cjs');
9
9
 
10
10
 
11
- exports.ScrollNavigator = _chunk6AMSRIQ4cjs.ScrollNavigator;
11
+ exports.ScrollNavigator = _chunkDMPSKGFMcjs.ScrollNavigator;
@@ -18,6 +18,8 @@ interface ScrollNavigatorScrollOptions {
18
18
  interface ScrollNavigatorProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
19
19
  items: readonly ScrollNavigatorItem[];
20
20
  label?: string;
21
+ /** Translucent, blurred floating controls with a solid fallback. Cards are unchanged. */
22
+ frosted?: boolean;
21
23
  /** Card columns above the small breakpoint; always one column on mobile. */
22
24
  columns?: 1 | 2 | 3 | 4;
23
25
  /** Space below a sticky header inside the scroll viewport, in pixels. */
@@ -18,6 +18,8 @@ interface ScrollNavigatorScrollOptions {
18
18
  interface ScrollNavigatorProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
19
19
  items: readonly ScrollNavigatorItem[];
20
20
  label?: string;
21
+ /** Translucent, blurred floating controls with a solid fallback. Cards are unchanged. */
22
+ frosted?: boolean;
21
23
  /** Card columns above the small breakpoint; always one column on mobile. */
22
24
  columns?: 1 | 2 | 3 | 4;
23
25
  /** Space below a sticky header inside the scroll viewport, in pixels. */
@@ -2,7 +2,7 @@
2
2
  "use client";
3
3
  import {
4
4
  ScrollNavigator
5
- } from "../chunk-V3XA7SDK.js";
5
+ } from "../chunk-36LNA4WM.js";
6
6
  import "../chunk-GA5SX7FE.js";
7
7
  import "../chunk-3OWGL4Q2.js";
8
8
  import "../chunk-G6SRSWXB.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicbagel/saas-kit",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "Accessible React primitives, layout components and design tokens for SaaS apps, built on Radix UI and Tailwind CSS v4.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -0,0 +1,38 @@
1
+ /* Opt-in chrome surfaces. Complete semantic colours keep every palette and
2
+ * mode working; unsupported blur, reduced transparency and forced colours
3
+ * retain an opaque, readable surface. No sizing or positioning lives here. */
4
+ @layer components {
5
+ [data-frosted-surface="background"] { --color-frosted-surface: var(--color-background); }
6
+ [data-frosted-surface="card"] { --color-frosted-surface: var(--color-card); }
7
+ [data-frosted-surface="muted"] { --color-frosted-surface: var(--color-muted); }
8
+
9
+ [data-frosted-surface] {
10
+ background-color: var(--color-frosted-surface);
11
+ }
12
+
13
+ @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
14
+ [data-frosted-surface] {
15
+ background-color: color-mix(in oklab, var(--color-frosted-surface) 70%, transparent);
16
+ -webkit-backdrop-filter: blur(16px);
17
+ backdrop-filter: blur(16px);
18
+ }
19
+ }
20
+
21
+ @media (prefers-reduced-transparency: reduce) {
22
+ [data-frosted-surface] {
23
+ background-color: var(--color-frosted-surface);
24
+ -webkit-backdrop-filter: none;
25
+ backdrop-filter: none;
26
+ }
27
+ }
28
+
29
+ @media (forced-colors: active) {
30
+ [data-frosted-surface] {
31
+ background-color: Canvas;
32
+ color: CanvasText;
33
+ border-color: CanvasText;
34
+ -webkit-backdrop-filter: none;
35
+ backdrop-filter: none;
36
+ }
37
+ }
38
+ }
@@ -4,3 +4,4 @@
4
4
  @import "./components/marker.css";
5
5
  @import "./components/message.css";
6
6
  @import "./components/message-scroller.css";
7
+ @import "./components/frosted-surface.css";