@epicbagel/saas-kit 1.28.0 → 1.30.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.
@@ -924,7 +924,7 @@ function MobileReplySuggestions({
924
924
  _chunk2BHI5ZWJcjs.DrawerContent,
925
925
  {
926
926
  "aria-describedby": void 0,
927
- className: "max-h-[min(85dvh,40rem)] overflow-hidden pb-[env(safe-area-inset-bottom)]",
927
+ className: "max-h-[min(85dvh,40rem)] overflow-hidden pb-[env(safe-area-inset-bottom)] md:mx-auto md:w-full md:max-w-xl",
928
928
  onOpenAutoFocus: (event) => {
929
929
  event.preventDefault();
930
930
  _optionalChain([closeRef, 'access', _11 => _11.current, 'optionalAccess', _12 => _12.focus, 'call', _13 => _13({ preventScroll: true })]);
@@ -999,6 +999,19 @@ function ExamplePromptsLoading({ label, className }) {
999
999
  function ChatError({ children, className }) {
1000
1000
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { "data-slot": "chat-error", role: "alert", className: _chunkKWLSBTB2cjs.cn.call(void 0, "mt-2 text-xs text-destructive", className), children });
1001
1001
  }
1002
+ var SHORT_VIEWPORT_HEIGHT = 900;
1003
+ function useIsShortViewport() {
1004
+ const [short, setShort] = _react.useState.call(void 0, false);
1005
+ _react.useEffect.call(void 0, () => {
1006
+ if (typeof window === "undefined") return;
1007
+ const mql = window.matchMedia(`(max-height: ${SHORT_VIEWPORT_HEIGHT - 1}px)`);
1008
+ const onChange = () => setShort(window.innerHeight < SHORT_VIEWPORT_HEIGHT);
1009
+ mql.addEventListener("change", onChange);
1010
+ onChange();
1011
+ return () => mql.removeEventListener("change", onChange);
1012
+ }, []);
1013
+ return short;
1014
+ }
1002
1015
  var Chat = _react.forwardRef.call(void 0, function Chat2({
1003
1016
  messages,
1004
1017
  onSend,
@@ -1127,7 +1140,9 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
1127
1140
  );
1128
1141
  const application = layout === "application";
1129
1142
  const workspace = layout === "workspace";
1130
- const compactSuggestions = mobileCompactSuggestions && isMobile;
1143
+ const shortViewport = useIsShortViewport();
1144
+ const compactSuggestions = mobileCompactSuggestions && (isMobile || shortViewport);
1145
+ const compactComposer = mobileCompactComposer && (isMobile || shortViewport);
1131
1146
  const showEmptyFileDropzone = emptyFileDropzone && Boolean(onFilesSelected) && attachments.length === 0;
1132
1147
  const liveVoiceActive = voiceSession && voiceSession.state !== "disconnected" && voiceSession.state !== "error";
1133
1148
  const showEmptyPending = !conversationStarted && pending && Boolean(emptyPendingContent) && !liveVoiceActive;
@@ -1313,7 +1328,9 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
1313
1328
  "pt-3",
1314
1329
  application && "px-4 py-3 md:bg-transparent md:px-0 md:pb-0 md:backdrop-blur-none md:supports-backdrop-filter:bg-transparent",
1315
1330
  workspace && "border-0 bg-gradient-to-t from-background via-background to-transparent px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-8 backdrop-blur-none supports-[backdrop-filter]:bg-gradient-to-t",
1316
- workspace && mobileCompactComposer && "max-md:pb-[max(0.75rem,env(safe-area-inset-bottom))] max-md:pt-3"
1331
+ workspace && mobileCompactComposer && "max-md:pb-[max(0.75rem,env(safe-area-inset-bottom))] max-md:pt-3",
1332
+ // A short window's dock is as tight as a phone's.
1333
+ workspace && compactComposer && "pb-[max(0.75rem,env(safe-area-inset-bottom))] pt-3"
1317
1334
  ),
1318
1335
  children: [
1319
1336
  !compactSuggestions && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -1361,7 +1378,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
1361
1378
  addFilesLabel,
1362
1379
  uploadingLabel,
1363
1380
  appearance: composerAppearance,
1364
- compactOnBlur: mobileCompactComposer && isMobile,
1381
+ compactOnBlur: compactComposer,
1365
1382
  helperText: composerHelperText,
1366
1383
  privacyNote: composerPrivacyNote,
1367
1384
  className: workspace ? "mx-auto max-w-3xl rounded-3xl shadow-sm" : void 0
@@ -232,7 +232,8 @@ interface ChatInputProps {
232
232
  /**
233
233
  * Fold an empty, unfocused composer into a short text row and control bar. Focusing its textarea
234
234
  * expands it; drafts and staged files always keep it expanded. Chat enables
235
- * this only for an active mobile conversation with `mobileCompactComposer`.
235
+ * this only for an active conversation on a phone or a short window, with
236
+ * `mobileCompactComposer`.
236
237
  */
237
238
  compactOnBlur?: boolean;
238
239
  /** Content inside the composer surface, above its files and text field. */
@@ -402,7 +403,10 @@ interface ChatProps {
402
403
  replySuggestions?: ReadonlyArray<string>;
403
404
  /** The reply suggestions' accessible name. Defaults to "Suggested replies". */
404
405
  replySuggestionsLabel?: string;
405
- /** Below 768px, show replies inside the composer as the first reply and a bottom-sheet picker. Desktop chips are unchanged. */
406
+ /**
407
+ * Below 768px wide, or 900px tall, show replies inside the composer as the
408
+ * first reply and a bottom-sheet picker. Taller desktop windows keep chips.
409
+ */
406
410
  mobileCompactSuggestions?: boolean;
407
411
  /** The mobile picker's opener, given the number of additional replies. Defaults to `+N more`. */
408
412
  replySuggestionsMoreLabel?: (count: number) => string;
@@ -411,9 +415,10 @@ interface ChatProps {
411
415
  /** Visual treatment for both empty and active text composers. */
412
416
  composerAppearance?: ChatInputProps["appearance"];
413
417
  /**
414
- * On phones (below 768px), fold an active conversation's empty composer
415
- * into a short text row above its controls until the textarea is focused. Drafts and files stay
416
- * expanded. Desktop and the new-conversation composer are unchanged.
418
+ * On phones (below 768px wide) and short windows (below 900px tall), fold an
419
+ * active conversation's empty composer into a short text row above its
420
+ * controls until the textarea is focused. Drafts and files stay expanded.
421
+ * Taller desktop windows and the new-conversation composer are unchanged.
417
422
  */
418
423
  mobileCompactComposer?: boolean;
419
424
  /** Quiet contextual copy inside the composer action row. */
@@ -232,7 +232,8 @@ interface ChatInputProps {
232
232
  /**
233
233
  * Fold an empty, unfocused composer into a short text row and control bar. Focusing its textarea
234
234
  * expands it; drafts and staged files always keep it expanded. Chat enables
235
- * this only for an active mobile conversation with `mobileCompactComposer`.
235
+ * this only for an active conversation on a phone or a short window, with
236
+ * `mobileCompactComposer`.
236
237
  */
237
238
  compactOnBlur?: boolean;
238
239
  /** Content inside the composer surface, above its files and text field. */
@@ -402,7 +403,10 @@ interface ChatProps {
402
403
  replySuggestions?: ReadonlyArray<string>;
403
404
  /** The reply suggestions' accessible name. Defaults to "Suggested replies". */
404
405
  replySuggestionsLabel?: string;
405
- /** Below 768px, show replies inside the composer as the first reply and a bottom-sheet picker. Desktop chips are unchanged. */
406
+ /**
407
+ * Below 768px wide, or 900px tall, show replies inside the composer as the
408
+ * first reply and a bottom-sheet picker. Taller desktop windows keep chips.
409
+ */
406
410
  mobileCompactSuggestions?: boolean;
407
411
  /** The mobile picker's opener, given the number of additional replies. Defaults to `+N more`. */
408
412
  replySuggestionsMoreLabel?: (count: number) => string;
@@ -411,9 +415,10 @@ interface ChatProps {
411
415
  /** Visual treatment for both empty and active text composers. */
412
416
  composerAppearance?: ChatInputProps["appearance"];
413
417
  /**
414
- * On phones (below 768px), fold an active conversation's empty composer
415
- * into a short text row above its controls until the textarea is focused. Drafts and files stay
416
- * expanded. Desktop and the new-conversation composer are unchanged.
418
+ * On phones (below 768px wide) and short windows (below 900px tall), fold an
419
+ * active conversation's empty composer into a short text row above its
420
+ * controls until the textarea is focused. Drafts and files stay expanded.
421
+ * Taller desktop windows and the new-conversation composer are unchanged.
417
422
  */
418
423
  mobileCompactComposer?: boolean;
419
424
  /** Quiet contextual copy inside the composer action row. */
@@ -924,7 +924,7 @@ function MobileReplySuggestions({
924
924
  DrawerContent,
925
925
  {
926
926
  "aria-describedby": void 0,
927
- className: "max-h-[min(85dvh,40rem)] overflow-hidden pb-[env(safe-area-inset-bottom)]",
927
+ className: "max-h-[min(85dvh,40rem)] overflow-hidden pb-[env(safe-area-inset-bottom)] md:mx-auto md:w-full md:max-w-xl",
928
928
  onOpenAutoFocus: (event) => {
929
929
  event.preventDefault();
930
930
  closeRef.current?.focus({ preventScroll: true });
@@ -999,6 +999,19 @@ function ExamplePromptsLoading({ label, className }) {
999
999
  function ChatError({ children, className }) {
1000
1000
  return /* @__PURE__ */ jsx("p", { "data-slot": "chat-error", role: "alert", className: cn("mt-2 text-xs text-destructive", className), children });
1001
1001
  }
1002
+ var SHORT_VIEWPORT_HEIGHT = 900;
1003
+ function useIsShortViewport() {
1004
+ const [short, setShort] = useState(false);
1005
+ useEffect(() => {
1006
+ if (typeof window === "undefined") return;
1007
+ const mql = window.matchMedia(`(max-height: ${SHORT_VIEWPORT_HEIGHT - 1}px)`);
1008
+ const onChange = () => setShort(window.innerHeight < SHORT_VIEWPORT_HEIGHT);
1009
+ mql.addEventListener("change", onChange);
1010
+ onChange();
1011
+ return () => mql.removeEventListener("change", onChange);
1012
+ }, []);
1013
+ return short;
1014
+ }
1002
1015
  var Chat = forwardRef(function Chat2({
1003
1016
  messages,
1004
1017
  onSend,
@@ -1127,7 +1140,9 @@ var Chat = forwardRef(function Chat2({
1127
1140
  );
1128
1141
  const application = layout === "application";
1129
1142
  const workspace = layout === "workspace";
1130
- const compactSuggestions = mobileCompactSuggestions && isMobile;
1143
+ const shortViewport = useIsShortViewport();
1144
+ const compactSuggestions = mobileCompactSuggestions && (isMobile || shortViewport);
1145
+ const compactComposer = mobileCompactComposer && (isMobile || shortViewport);
1131
1146
  const showEmptyFileDropzone = emptyFileDropzone && Boolean(onFilesSelected) && attachments.length === 0;
1132
1147
  const liveVoiceActive = voiceSession && voiceSession.state !== "disconnected" && voiceSession.state !== "error";
1133
1148
  const showEmptyPending = !conversationStarted && pending && Boolean(emptyPendingContent) && !liveVoiceActive;
@@ -1313,7 +1328,9 @@ var Chat = forwardRef(function Chat2({
1313
1328
  "pt-3",
1314
1329
  application && "px-4 py-3 md:bg-transparent md:px-0 md:pb-0 md:backdrop-blur-none md:supports-backdrop-filter:bg-transparent",
1315
1330
  workspace && "border-0 bg-gradient-to-t from-background via-background to-transparent px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-8 backdrop-blur-none supports-[backdrop-filter]:bg-gradient-to-t",
1316
- workspace && mobileCompactComposer && "max-md:pb-[max(0.75rem,env(safe-area-inset-bottom))] max-md:pt-3"
1331
+ workspace && mobileCompactComposer && "max-md:pb-[max(0.75rem,env(safe-area-inset-bottom))] max-md:pt-3",
1332
+ // A short window's dock is as tight as a phone's.
1333
+ workspace && compactComposer && "pb-[max(0.75rem,env(safe-area-inset-bottom))] pt-3"
1317
1334
  ),
1318
1335
  children: [
1319
1336
  !compactSuggestions && /* @__PURE__ */ jsx(
@@ -1361,7 +1378,7 @@ var Chat = forwardRef(function Chat2({
1361
1378
  addFilesLabel,
1362
1379
  uploadingLabel,
1363
1380
  appearance: composerAppearance,
1364
- compactOnBlur: mobileCompactComposer && isMobile,
1381
+ compactOnBlur: compactComposer,
1365
1382
  helperText: composerHelperText,
1366
1383
  privacyNote: composerPrivacyNote,
1367
1384
  className: workspace ? "mx-auto max-w-3xl rounded-3xl shadow-sm" : void 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicbagel/saas-kit",
3
- "version": "1.28.0",
3
+ "version": "1.30.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": [