@epicbagel/saas-kit 1.6.0 → 1.7.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
@@ -94,7 +94,8 @@ public in 1.1.0, and these were the decisions about its props then:
94
94
  ("Thinking…"), `scrollButtonLabel` ("Scroll to latest message"),
95
95
  `uploadingLabel` ("Uploading…"), `voiceHeroLabel` ("Tap to speak"),
96
96
  `voiceHeroSwitchLabel` ("or type instead"), `examplePromptsLabel` ("Try"),
97
- `examplePromptsLoadingLabel` ("Loading suggestions…"), `addFilesLabel`,
97
+ `examplePromptsLoadingLabel` ("Loading suggestions…"),
98
+ `replySuggestionsLabel` ("Suggested replies"), `addFilesLabel`,
98
99
  `placeholder` and `activePlaceholder`. The defaults are plain English with
99
100
  no product voice, and the empty state has no title unless you pass
100
101
  `emptyTitle`. The icon buttons' accessible names ("Send", "Add to
@@ -114,6 +115,15 @@ public in 1.1.0, and these were the decisions about its props then:
114
115
  - **Example prompts.** `examplePromptsVariant` is `chips` (the default: chips
115
116
  that wrap onto as many lines as they need), `row` (one line that scrolls
116
117
  sideways) or `cards` (a two-column grid).
118
+ - **Reply suggestions (1.7.0).** `replySuggestions` are the replies the
119
+ conversation offers next. They show once the conversation has started, at
120
+ the composer, directly above the input, as the example prompts' chips (one
121
+ implementation draws both), and wrap rather than cover the input on a
122
+ phone. `replySuggestionsLabel` ("Suggested replies") names the group.
123
+ Picking a suggestion of either kind sends only its words, `onSend(text,
124
+ [])`: the draft and staged files stay, and a failure is reported without
125
+ touching them. Suggestions are disabled while `pending`; pass none once
126
+ they no longer apply.
117
127
  - **Failed sends.** When `onSend` rejects, Chat puts the text back in the
118
128
  composer and calls `onSendError`; without one it reports the rejection
119
129
  through `reportError`, so a failure is never silent.
@@ -12,7 +12,7 @@ declare function Attachment({ className, state, size, orientation, ...props }: R
12
12
  state?: "idle" | "uploading" | "processing" | "error" | "done";
13
13
  }): react_jsx_runtime.JSX.Element;
14
14
  declare const attachmentMediaVariants: (props?: ({
15
- variant?: "image" | "icon" | null | undefined;
15
+ variant?: "icon" | "image" | null | undefined;
16
16
  } & class_variance_authority_types.ClassProp) | undefined) => string;
17
17
  declare function AttachmentMedia({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof attachmentMediaVariants>): react_jsx_runtime.JSX.Element;
18
18
  declare function AttachmentContent({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
@@ -12,7 +12,7 @@ declare function Attachment({ className, state, size, orientation, ...props }: R
12
12
  state?: "idle" | "uploading" | "processing" | "error" | "done";
13
13
  }): react_jsx_runtime.JSX.Element;
14
14
  declare const attachmentMediaVariants: (props?: ({
15
- variant?: "image" | "icon" | null | undefined;
15
+ variant?: "icon" | "image" | null | undefined;
16
16
  } & class_variance_authority_types.ClassProp) | undefined) => string;
17
17
  declare function AttachmentMedia({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof attachmentMediaVariants>): react_jsx_runtime.JSX.Element;
18
18
  declare function AttachmentContent({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
@@ -4,7 +4,7 @@ import { VariantProps } from 'class-variance-authority';
4
4
  import { ElementType } from 'react';
5
5
 
6
6
  declare const badgeVariants: (props?: ({
7
- variant?: "neutral" | "error" | "success" | "default" | "secondary" | "destructive" | "outline" | "warning" | "info" | null | undefined;
7
+ variant?: "error" | "success" | "default" | "secondary" | "destructive" | "outline" | "warning" | "info" | "neutral" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
10
10
  weight?: "normal" | "semibold" | "bold";
@@ -4,7 +4,7 @@ import { VariantProps } from 'class-variance-authority';
4
4
  import { ElementType } from 'react';
5
5
 
6
6
  declare const badgeVariants: (props?: ({
7
- variant?: "neutral" | "error" | "success" | "default" | "secondary" | "destructive" | "outline" | "warning" | "info" | null | undefined;
7
+ variant?: "error" | "success" | "default" | "secondary" | "destructive" | "outline" | "warning" | "info" | "neutral" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
10
10
  weight?: "normal" | "semibold" | "bold";
@@ -5,7 +5,7 @@ import { VariantProps } from 'class-variance-authority';
5
5
 
6
6
  declare function BubbleGroup({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
7
7
  declare const bubbleVariants: (props?: ({
8
- variant?: "default" | "muted" | "ghost" | "secondary" | "destructive" | "outline" | "tinted" | null | undefined;
8
+ variant?: "muted" | "default" | "secondary" | "destructive" | "outline" | "ghost" | "tinted" | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  declare function Bubble({ variant, align, className, ...props }: React.ComponentProps<"div"> & VariantProps<typeof bubbleVariants> & {
11
11
  align?: "start" | "end";
@@ -5,7 +5,7 @@ import { VariantProps } from 'class-variance-authority';
5
5
 
6
6
  declare function BubbleGroup({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
7
7
  declare const bubbleVariants: (props?: ({
8
- variant?: "default" | "muted" | "ghost" | "secondary" | "destructive" | "outline" | "tinted" | null | undefined;
8
+ variant?: "muted" | "default" | "secondary" | "destructive" | "outline" | "ghost" | "tinted" | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  declare function Bubble({ variant, align, className, ...props }: React.ComponentProps<"div"> & VariantProps<typeof bubbleVariants> & {
11
11
  align?: "start" | "end";
@@ -4,13 +4,13 @@ import * as class_variance_authority_types from 'class-variance-authority/types'
4
4
  import { VariantProps } from 'class-variance-authority';
5
5
 
6
6
  declare const buttonVariants: (props?: ({
7
- variant?: "link" | "default" | "ghost" | "secondary" | "destructive" | "outline" | "outline-muted" | "quiet" | "ghost-destructive" | "link-muted" | "inverted" | "status-error" | "status-warning" | "status-muted" | null | undefined;
8
- size?: "filter" | "status" | "toolbar" | "sm" | "lg" | "default" | "micro" | "tone" | "compact" | "hero" | "icon" | "icon-lg" | "icon-sm" | "icon-md" | "icon-xs" | "icon-compact" | "icon-micro" | "bare" | "voice-sm" | "voice" | "suggestion" | null | undefined;
7
+ variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "outline-muted" | "ghost" | "quiet" | "ghost-destructive" | "link-muted" | "inverted" | "status-error" | "status-warning" | "status-muted" | null | undefined;
8
+ size?: "filter" | "status" | "icon" | "toolbar" | "sm" | "lg" | "default" | "micro" | "compact" | "hero" | "icon-lg" | "icon-sm" | "icon-md" | "icon-xs" | "icon-compact" | "icon-micro" | "bare" | "voice-sm" | "voice" | "suggestion" | "tone" | null | undefined;
9
9
  width?: "auto" | "full" | "responsive" | null | undefined;
10
10
  align?: "center" | "start" | null | undefined;
11
11
  shrink?: boolean | null | undefined;
12
12
  shape?: "circle" | "default" | "pill" | null | undefined;
13
- ring?: "none" | "surface" | null | undefined;
13
+ ring?: "surface" | "none" | null | undefined;
14
14
  grow?: boolean | null | undefined;
15
15
  minWidth?: "auto" | "zero" | null | undefined;
16
16
  textAlign?: "center" | "start" | null | undefined;
@@ -4,13 +4,13 @@ import * as class_variance_authority_types from 'class-variance-authority/types'
4
4
  import { VariantProps } from 'class-variance-authority';
5
5
 
6
6
  declare const buttonVariants: (props?: ({
7
- variant?: "link" | "default" | "ghost" | "secondary" | "destructive" | "outline" | "outline-muted" | "quiet" | "ghost-destructive" | "link-muted" | "inverted" | "status-error" | "status-warning" | "status-muted" | null | undefined;
8
- size?: "filter" | "status" | "toolbar" | "sm" | "lg" | "default" | "micro" | "tone" | "compact" | "hero" | "icon" | "icon-lg" | "icon-sm" | "icon-md" | "icon-xs" | "icon-compact" | "icon-micro" | "bare" | "voice-sm" | "voice" | "suggestion" | null | undefined;
7
+ variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "outline-muted" | "ghost" | "quiet" | "ghost-destructive" | "link-muted" | "inverted" | "status-error" | "status-warning" | "status-muted" | null | undefined;
8
+ size?: "filter" | "status" | "icon" | "toolbar" | "sm" | "lg" | "default" | "micro" | "compact" | "hero" | "icon-lg" | "icon-sm" | "icon-md" | "icon-xs" | "icon-compact" | "icon-micro" | "bare" | "voice-sm" | "voice" | "suggestion" | "tone" | null | undefined;
9
9
  width?: "auto" | "full" | "responsive" | null | undefined;
10
10
  align?: "center" | "start" | null | undefined;
11
11
  shrink?: boolean | null | undefined;
12
12
  shape?: "circle" | "default" | "pill" | null | undefined;
13
- ring?: "none" | "surface" | null | undefined;
13
+ ring?: "surface" | "none" | null | undefined;
14
14
  grow?: boolean | null | undefined;
15
15
  minWidth?: "auto" | "zero" | null | undefined;
16
16
  textAlign?: "center" | "start" | null | undefined;
@@ -629,27 +629,40 @@ var EXAMPLE_PROMPT_LAYOUTS = {
629
629
  row: "row",
630
630
  cards: "grid"
631
631
  };
632
- function ExamplePrompts({
633
- prompts,
632
+ function ChatSuggestions({
633
+ slot,
634
+ items,
634
635
  onSelect,
635
- label = "Try",
636
+ label,
637
+ showLabel = true,
636
638
  variant = "chips",
639
+ disabled = false,
637
640
  className
638
641
  }) {
639
642
  const labelId = _react.useId.call(void 0, );
640
- if (prompts.length === 0) return null;
643
+ if (items.length === 0) return null;
641
644
  const cards = variant === "cards";
642
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { "data-slot": "chat-examples", "data-variant": variant, className: _chunkKWLSBTB2cjs.cn.call(void 0, cards ? "mt-4" : "mt-3", className), children: [
643
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { id: labelId, "data-slot": "chat-examples-label", className: "mb-2 text-left text-xs text-muted-foreground", children: label }),
644
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkM4V7OJNFcjs.Suggestions, { role: "group", "aria-labelledby": labelId, layout: EXAMPLE_PROMPT_LAYOUTS[variant], children: prompts.map((prompt) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
645
- _chunkM4V7OJNFcjs.Suggestion,
645
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { "data-slot": slot, "data-variant": variant, className, children: [
646
+ showLabel && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { id: labelId, "data-slot": `${slot}-label`, className: "mb-2 text-left text-xs text-muted-foreground", children: label }),
647
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
648
+ _chunkM4V7OJNFcjs.Suggestions,
646
649
  {
647
- suggestion: prompt,
648
- onClick: onSelect,
649
- className: cards ? "h-auto min-h-12 px-3 py-2.5 text-xs" : void 0
650
- },
651
- prompt
652
- )) })
650
+ role: "group",
651
+ "aria-labelledby": showLabel ? labelId : void 0,
652
+ "aria-label": showLabel ? void 0 : label,
653
+ layout: EXAMPLE_PROMPT_LAYOUTS[variant],
654
+ children: items.map((item) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
655
+ _chunkM4V7OJNFcjs.Suggestion,
656
+ {
657
+ suggestion: item,
658
+ onClick: onSelect,
659
+ disabled,
660
+ className: cards ? "h-auto min-h-12 px-3 py-2.5 text-xs" : void 0
661
+ },
662
+ item
663
+ ))
664
+ }
665
+ )
653
666
  ] });
654
667
  }
655
668
  function ExamplePromptsLoading({ label, className }) {
@@ -693,6 +706,8 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
693
706
  examplePromptsVariant = "chips",
694
707
  examplePromptsLoading = false,
695
708
  examplePromptsLoadingLabel = "Loading suggestions\u2026",
709
+ replySuggestions = [],
710
+ replySuggestionsLabel = "Suggested replies",
696
711
  compactEmptyState = false,
697
712
  defaultMode,
698
713
  placeholder = "Type your message\u2026",
@@ -739,6 +754,14 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
739
754
  const conversationStarted = messages.length > 0;
740
755
  const [userMode, setUserMode] = _react.useState.call(void 0, null);
741
756
  const mode = _nullishCoalesce(_nullishCoalesce(userMode, () => ( defaultMode)), () => ( (isMobile ? "voice" : "text")));
757
+ const reportSendError = _react.useCallback.call(void 0,
758
+ (cause) => {
759
+ if (onSendError) onSendError(cause);
760
+ else if (typeof globalThis.reportError === "function") globalThis.reportError(cause);
761
+ else console.error(cause);
762
+ },
763
+ [onSendError]
764
+ );
742
765
  const send = _react.useCallback.call(void 0,
743
766
  async (text, sentAttachments = attachments) => {
744
767
  const trimmed = text.trim();
@@ -751,18 +774,29 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
751
774
  setInput((current) => current ? current : trimmed);
752
775
  if (mode === "voice") setUserMode("text");
753
776
  }
754
- if (onSendError) onSendError(cause);
755
- else if (typeof globalThis.reportError === "function") globalThis.reportError(cause);
756
- else console.error(cause);
777
+ reportSendError(cause);
757
778
  }
758
779
  window.setTimeout(() => _optionalChain([inputRef, 'access', _9 => _9.current, 'optionalAccess', _10 => _10.focus, 'call', _11 => _11()]), 0);
759
780
  },
760
- [attachments, mode, onSend, onSendError, pending]
781
+ [attachments, mode, onSend, pending, reportSendError]
782
+ );
783
+ const sendSuggestion = _react.useCallback.call(void 0,
784
+ async (text) => {
785
+ const trimmed = text.trim();
786
+ if (!trimmed || pending) return;
787
+ try {
788
+ await onSend(trimmed, []);
789
+ } catch (cause) {
790
+ reportSendError(cause);
791
+ }
792
+ window.setTimeout(() => _optionalChain([inputRef, 'access', _12 => _12.current, 'optionalAccess', _13 => _13.focus, 'call', _14 => _14()]), 0);
793
+ },
794
+ [onSend, pending, reportSendError]
761
795
  );
762
796
  _react.useImperativeHandle.call(void 0,
763
797
  ref,
764
798
  () => ({
765
- focusInput: () => _optionalChain([inputRef, 'access', _12 => _12.current, 'optionalAccess', _13 => _13.focus, 'call', _14 => _14()]),
799
+ focusInput: () => _optionalChain([inputRef, 'access', _15 => _15.current, 'optionalAccess', _16 => _16.focus, 'call', _17 => _17()]),
766
800
  clearInput: () => setInput("")
767
801
  }),
768
802
  []
@@ -770,7 +804,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
770
804
  const dropFiles = _react.useCallback.call(void 0,
771
805
  (files) => {
772
806
  if (!conversationStarted && mode === "voice") setUserMode("text");
773
- return _optionalChain([onFilesSelected, 'optionalCall', _15 => _15(files)]);
807
+ return _optionalChain([onFilesSelected, 'optionalCall', _18 => _18(files)]);
774
808
  },
775
809
  [conversationStarted, mode, onFilesSelected]
776
810
  );
@@ -898,12 +932,14 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
898
932
  error && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ChatError, { children: error }),
899
933
  !pending && examplePromptsLoading && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ExamplePromptsLoading, { label: examplePromptsLoadingLabel }),
900
934
  !pending && !examplePromptsLoading && examplePrompts.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
901
- ExamplePrompts,
935
+ ChatSuggestions,
902
936
  {
903
- prompts: examplePrompts,
904
- onSelect: (p) => void send(p),
937
+ slot: "chat-examples",
938
+ items: examplePrompts,
939
+ onSelect: (prompt) => void sendSuggestion(prompt),
905
940
  label: examplePromptsLabel,
906
- variant: examplePromptsVariant
941
+ variant: examplePromptsVariant,
942
+ className: examplePromptsVariant === "cards" ? "mt-4" : "mt-3"
907
943
  }
908
944
  ),
909
945
  emptyFooter && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { "data-slot": "chat-empty-footer", className: "mt-5", children: emptyFooter })
@@ -946,6 +982,18 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
946
982
  workspace && mobileCompactComposer && "max-md:pb-[max(0.75rem,env(safe-area-inset-bottom))] max-md:pt-3"
947
983
  ),
948
984
  children: [
985
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
986
+ ChatSuggestions,
987
+ {
988
+ slot: "chat-replies",
989
+ items: replySuggestions,
990
+ onSelect: (reply) => void sendSuggestion(reply),
991
+ label: replySuggestionsLabel,
992
+ showLabel: false,
993
+ disabled: pending,
994
+ className: _chunkKWLSBTB2cjs.cn.call(void 0, "mb-3", workspace && "mx-auto max-w-3xl")
995
+ }
996
+ ),
949
997
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
950
998
  ChatInput,
951
999
  {
@@ -249,7 +249,10 @@ interface ChatProps {
249
249
  emptyTitle?: ReactNode;
250
250
  /** Subheadline shown under the headline. */
251
251
  emptySubtitle?: ReactNode;
252
- /** Optional list of suggested starter prompts. Click → sends. */
252
+ /**
253
+ * Optional list of suggested starter prompts, under the empty-state
254
+ * composer. Picking one sends it as `replySuggestions` do.
255
+ */
253
256
  examplePrompts?: ReadonlyArray<string>;
254
257
  /** Label above the example prompts. Defaults to "Try". */
255
258
  examplePromptsLabel?: string;
@@ -303,6 +306,19 @@ interface ChatProps {
303
306
  addFilesLabel?: string;
304
307
  /** Shown on a file, staged or on a message, while it is still uploading. Defaults to "Uploading…". */
305
308
  uploadingLabel?: string;
309
+ /**
310
+ * Replies the conversation offers next, such as the choices the latest
311
+ * assistant turn asked for. Once the conversation has started they show
312
+ * at the composer, directly above the input and at its width, as the same
313
+ * suggestion chips as `examplePrompts`, wrapping onto as many lines as
314
+ * they need. Picking one sends only its words: the draft and any staged
315
+ * files stay in the composer, and a failed send is reported
316
+ * (`onSendError`) without touching them. Disabled while `pending`. Pass
317
+ * none (the default) to show none; drop them once they no longer apply.
318
+ */
319
+ replySuggestions?: ReadonlyArray<string>;
320
+ /** The reply suggestions' accessible name. Defaults to "Suggested replies". */
321
+ replySuggestionsLabel?: string;
306
322
  /** Visual treatment for both empty and active text composers. */
307
323
  composerAppearance?: ChatInputProps["appearance"];
308
324
  /**
@@ -406,7 +422,8 @@ interface ChatHandle {
406
422
  * - Optional example prompts render as suggestions below the input.
407
423
  *
408
424
  * **Active state** (messages exist) — always the compact text input;
409
- * the mic stays as an accessory icon.
425
+ * the mic stays as an accessory icon. Optional reply suggestions render as
426
+ * the same suggestions, directly above the input.
410
427
  *
411
428
  * **Files** — with `onFilesSelected`, the composer's menu picks files and a
412
429
  * file dragged anywhere on the conversation can be dropped (`fileDrop`);
@@ -249,7 +249,10 @@ interface ChatProps {
249
249
  emptyTitle?: ReactNode;
250
250
  /** Subheadline shown under the headline. */
251
251
  emptySubtitle?: ReactNode;
252
- /** Optional list of suggested starter prompts. Click → sends. */
252
+ /**
253
+ * Optional list of suggested starter prompts, under the empty-state
254
+ * composer. Picking one sends it as `replySuggestions` do.
255
+ */
253
256
  examplePrompts?: ReadonlyArray<string>;
254
257
  /** Label above the example prompts. Defaults to "Try". */
255
258
  examplePromptsLabel?: string;
@@ -303,6 +306,19 @@ interface ChatProps {
303
306
  addFilesLabel?: string;
304
307
  /** Shown on a file, staged or on a message, while it is still uploading. Defaults to "Uploading…". */
305
308
  uploadingLabel?: string;
309
+ /**
310
+ * Replies the conversation offers next, such as the choices the latest
311
+ * assistant turn asked for. Once the conversation has started they show
312
+ * at the composer, directly above the input and at its width, as the same
313
+ * suggestion chips as `examplePrompts`, wrapping onto as many lines as
314
+ * they need. Picking one sends only its words: the draft and any staged
315
+ * files stay in the composer, and a failed send is reported
316
+ * (`onSendError`) without touching them. Disabled while `pending`. Pass
317
+ * none (the default) to show none; drop them once they no longer apply.
318
+ */
319
+ replySuggestions?: ReadonlyArray<string>;
320
+ /** The reply suggestions' accessible name. Defaults to "Suggested replies". */
321
+ replySuggestionsLabel?: string;
306
322
  /** Visual treatment for both empty and active text composers. */
307
323
  composerAppearance?: ChatInputProps["appearance"];
308
324
  /**
@@ -406,7 +422,8 @@ interface ChatHandle {
406
422
  * - Optional example prompts render as suggestions below the input.
407
423
  *
408
424
  * **Active state** (messages exist) — always the compact text input;
409
- * the mic stays as an accessory icon.
425
+ * the mic stays as an accessory icon. Optional reply suggestions render as
426
+ * the same suggestions, directly above the input.
410
427
  *
411
428
  * **Files** — with `onFilesSelected`, the composer's menu picks files and a
412
429
  * file dragged anywhere on the conversation can be dropped (`fileDrop`);
@@ -629,27 +629,40 @@ var EXAMPLE_PROMPT_LAYOUTS = {
629
629
  row: "row",
630
630
  cards: "grid"
631
631
  };
632
- function ExamplePrompts({
633
- prompts,
632
+ function ChatSuggestions({
633
+ slot,
634
+ items,
634
635
  onSelect,
635
- label = "Try",
636
+ label,
637
+ showLabel = true,
636
638
  variant = "chips",
639
+ disabled = false,
637
640
  className
638
641
  }) {
639
642
  const labelId = useId();
640
- if (prompts.length === 0) return null;
643
+ if (items.length === 0) return null;
641
644
  const cards = variant === "cards";
642
- return /* @__PURE__ */ jsxs("div", { "data-slot": "chat-examples", "data-variant": variant, className: cn(cards ? "mt-4" : "mt-3", className), children: [
643
- /* @__PURE__ */ jsx("p", { id: labelId, "data-slot": "chat-examples-label", className: "mb-2 text-left text-xs text-muted-foreground", children: label }),
644
- /* @__PURE__ */ jsx(Suggestions, { role: "group", "aria-labelledby": labelId, layout: EXAMPLE_PROMPT_LAYOUTS[variant], children: prompts.map((prompt) => /* @__PURE__ */ jsx(
645
- Suggestion,
645
+ return /* @__PURE__ */ jsxs("div", { "data-slot": slot, "data-variant": variant, className, children: [
646
+ showLabel && /* @__PURE__ */ jsx("p", { id: labelId, "data-slot": `${slot}-label`, className: "mb-2 text-left text-xs text-muted-foreground", children: label }),
647
+ /* @__PURE__ */ jsx(
648
+ Suggestions,
646
649
  {
647
- suggestion: prompt,
648
- onClick: onSelect,
649
- className: cards ? "h-auto min-h-12 px-3 py-2.5 text-xs" : void 0
650
- },
651
- prompt
652
- )) })
650
+ role: "group",
651
+ "aria-labelledby": showLabel ? labelId : void 0,
652
+ "aria-label": showLabel ? void 0 : label,
653
+ layout: EXAMPLE_PROMPT_LAYOUTS[variant],
654
+ children: items.map((item) => /* @__PURE__ */ jsx(
655
+ Suggestion,
656
+ {
657
+ suggestion: item,
658
+ onClick: onSelect,
659
+ disabled,
660
+ className: cards ? "h-auto min-h-12 px-3 py-2.5 text-xs" : void 0
661
+ },
662
+ item
663
+ ))
664
+ }
665
+ )
653
666
  ] });
654
667
  }
655
668
  function ExamplePromptsLoading({ label, className }) {
@@ -693,6 +706,8 @@ var Chat = forwardRef(function Chat2({
693
706
  examplePromptsVariant = "chips",
694
707
  examplePromptsLoading = false,
695
708
  examplePromptsLoadingLabel = "Loading suggestions\u2026",
709
+ replySuggestions = [],
710
+ replySuggestionsLabel = "Suggested replies",
696
711
  compactEmptyState = false,
697
712
  defaultMode,
698
713
  placeholder = "Type your message\u2026",
@@ -739,6 +754,14 @@ var Chat = forwardRef(function Chat2({
739
754
  const conversationStarted = messages.length > 0;
740
755
  const [userMode, setUserMode] = useState(null);
741
756
  const mode = userMode ?? defaultMode ?? (isMobile ? "voice" : "text");
757
+ const reportSendError = useCallback(
758
+ (cause) => {
759
+ if (onSendError) onSendError(cause);
760
+ else if (typeof globalThis.reportError === "function") globalThis.reportError(cause);
761
+ else console.error(cause);
762
+ },
763
+ [onSendError]
764
+ );
742
765
  const send = useCallback(
743
766
  async (text, sentAttachments = attachments) => {
744
767
  const trimmed = text.trim();
@@ -751,13 +774,24 @@ var Chat = forwardRef(function Chat2({
751
774
  setInput((current) => current ? current : trimmed);
752
775
  if (mode === "voice") setUserMode("text");
753
776
  }
754
- if (onSendError) onSendError(cause);
755
- else if (typeof globalThis.reportError === "function") globalThis.reportError(cause);
756
- else console.error(cause);
777
+ reportSendError(cause);
778
+ }
779
+ window.setTimeout(() => inputRef.current?.focus(), 0);
780
+ },
781
+ [attachments, mode, onSend, pending, reportSendError]
782
+ );
783
+ const sendSuggestion = useCallback(
784
+ async (text) => {
785
+ const trimmed = text.trim();
786
+ if (!trimmed || pending) return;
787
+ try {
788
+ await onSend(trimmed, []);
789
+ } catch (cause) {
790
+ reportSendError(cause);
757
791
  }
758
792
  window.setTimeout(() => inputRef.current?.focus(), 0);
759
793
  },
760
- [attachments, mode, onSend, onSendError, pending]
794
+ [onSend, pending, reportSendError]
761
795
  );
762
796
  useImperativeHandle(
763
797
  ref,
@@ -898,12 +932,14 @@ var Chat = forwardRef(function Chat2({
898
932
  error && /* @__PURE__ */ jsx(ChatError, { children: error }),
899
933
  !pending && examplePromptsLoading && /* @__PURE__ */ jsx(ExamplePromptsLoading, { label: examplePromptsLoadingLabel }),
900
934
  !pending && !examplePromptsLoading && examplePrompts.length > 0 && /* @__PURE__ */ jsx(
901
- ExamplePrompts,
935
+ ChatSuggestions,
902
936
  {
903
- prompts: examplePrompts,
904
- onSelect: (p) => void send(p),
937
+ slot: "chat-examples",
938
+ items: examplePrompts,
939
+ onSelect: (prompt) => void sendSuggestion(prompt),
905
940
  label: examplePromptsLabel,
906
- variant: examplePromptsVariant
941
+ variant: examplePromptsVariant,
942
+ className: examplePromptsVariant === "cards" ? "mt-4" : "mt-3"
907
943
  }
908
944
  ),
909
945
  emptyFooter && /* @__PURE__ */ jsx("div", { "data-slot": "chat-empty-footer", className: "mt-5", children: emptyFooter })
@@ -946,6 +982,18 @@ var Chat = forwardRef(function Chat2({
946
982
  workspace && mobileCompactComposer && "max-md:pb-[max(0.75rem,env(safe-area-inset-bottom))] max-md:pt-3"
947
983
  ),
948
984
  children: [
985
+ /* @__PURE__ */ jsx(
986
+ ChatSuggestions,
987
+ {
988
+ slot: "chat-replies",
989
+ items: replySuggestions,
990
+ onSelect: (reply) => void sendSuggestion(reply),
991
+ label: replySuggestionsLabel,
992
+ showLabel: false,
993
+ disabled: pending,
994
+ className: cn("mb-3", workspace && "mx-auto max-w-3xl")
995
+ }
996
+ ),
949
997
  /* @__PURE__ */ jsx(
950
998
  ChatInput,
951
999
  {
@@ -8,14 +8,14 @@ import '@radix-ui/react-separator';
8
8
  declare function ItemGroup({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
9
9
  declare function ItemSeparator({ className, ...props }: React.ComponentProps<typeof Separator>): react_jsx_runtime.JSX.Element;
10
10
  declare const itemVariants: (props?: ({
11
- variant?: "default" | "muted" | "outline" | null | undefined;
11
+ variant?: "muted" | "default" | "outline" | null | undefined;
12
12
  size?: "xs" | "sm" | "default" | null | undefined;
13
13
  } & class_variance_authority_types.ClassProp) | undefined) => string;
14
14
  declare function Item({ className, variant, size, asChild, ...props }: React.ComponentProps<"div"> & VariantProps<typeof itemVariants> & {
15
15
  asChild?: boolean;
16
16
  }): react_jsx_runtime.JSX.Element;
17
17
  declare const itemMediaVariants: (props?: ({
18
- variant?: "image" | "default" | "icon" | null | undefined;
18
+ variant?: "icon" | "image" | "default" | null | undefined;
19
19
  } & class_variance_authority_types.ClassProp) | undefined) => string;
20
20
  declare function ItemMedia({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>): react_jsx_runtime.JSX.Element;
21
21
  declare function ItemContent({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
@@ -8,14 +8,14 @@ import '@radix-ui/react-separator';
8
8
  declare function ItemGroup({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
9
9
  declare function ItemSeparator({ className, ...props }: React.ComponentProps<typeof Separator>): react_jsx_runtime.JSX.Element;
10
10
  declare const itemVariants: (props?: ({
11
- variant?: "default" | "muted" | "outline" | null | undefined;
11
+ variant?: "muted" | "default" | "outline" | null | undefined;
12
12
  size?: "xs" | "sm" | "default" | null | undefined;
13
13
  } & class_variance_authority_types.ClassProp) | undefined) => string;
14
14
  declare function Item({ className, variant, size, asChild, ...props }: React.ComponentProps<"div"> & VariantProps<typeof itemVariants> & {
15
15
  asChild?: boolean;
16
16
  }): react_jsx_runtime.JSX.Element;
17
17
  declare const itemMediaVariants: (props?: ({
18
- variant?: "image" | "default" | "icon" | null | undefined;
18
+ variant?: "icon" | "image" | "default" | null | undefined;
19
19
  } & class_variance_authority_types.ClassProp) | undefined) => string;
20
20
  declare function ItemMedia({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>): react_jsx_runtime.JSX.Element;
21
21
  declare function ItemContent({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
@@ -5,7 +5,7 @@ import { VariantProps } from 'class-variance-authority';
5
5
 
6
6
  declare const sectionHeaderVariants: (props?: ({
7
7
  variant?: "muted" | "plain" | null | undefined;
8
- spacing?: "none" | "tight" | "loose" | null | undefined;
8
+ spacing?: "tight" | "loose" | "none" | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  interface SectionHeaderProps extends Omit<HTMLAttributes<HTMLHeadingElement>, "children">, VariantProps<typeof sectionHeaderVariants> {
11
11
  /** Pre-rendered icon node. Sized to match the header text. */
@@ -5,7 +5,7 @@ import { VariantProps } from 'class-variance-authority';
5
5
 
6
6
  declare const sectionHeaderVariants: (props?: ({
7
7
  variant?: "muted" | "plain" | null | undefined;
8
- spacing?: "none" | "tight" | "loose" | null | undefined;
8
+ spacing?: "tight" | "loose" | "none" | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  interface SectionHeaderProps extends Omit<HTMLAttributes<HTMLHeadingElement>, "children">, VariantProps<typeof sectionHeaderVariants> {
11
11
  /** Pre-rendered icon node. Sized to match the header text. */
@@ -5,11 +5,11 @@ import { VariantProps } from 'class-variance-authority';
5
5
 
6
6
  declare const ToggleGroup: React.ForwardRefExoticComponent<((Omit<ToggleGroupPrimitive.ToggleGroupSingleProps & React.RefAttributes<HTMLDivElement>, "ref"> | Omit<ToggleGroupPrimitive.ToggleGroupMultipleProps & React.RefAttributes<HTMLDivElement>, "ref">) & VariantProps<(props?: ({
7
7
  variant?: "default" | "outline" | null | undefined;
8
- size?: "sm" | "lg" | "default" | "icon" | "icon-lg" | "avatar" | "chip" | null | undefined;
8
+ size?: "icon" | "sm" | "lg" | "default" | "icon-lg" | "avatar" | "chip" | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string>) & React.RefAttributes<HTMLDivElement>>;
10
10
  declare const ToggleGroupItem: React.ForwardRefExoticComponent<Omit<ToggleGroupPrimitive.ToggleGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & VariantProps<(props?: ({
11
11
  variant?: "default" | "outline" | null | undefined;
12
- size?: "sm" | "lg" | "default" | "icon" | "icon-lg" | "avatar" | "chip" | null | undefined;
12
+ size?: "icon" | "sm" | "lg" | "default" | "icon-lg" | "avatar" | "chip" | null | undefined;
13
13
  } & class_variance_authority_types.ClassProp) | undefined) => string> & {
14
14
  grow?: boolean;
15
15
  textSize?: "inherit" | "xs";
@@ -5,11 +5,11 @@ import { VariantProps } from 'class-variance-authority';
5
5
 
6
6
  declare const ToggleGroup: React.ForwardRefExoticComponent<((Omit<ToggleGroupPrimitive.ToggleGroupSingleProps & React.RefAttributes<HTMLDivElement>, "ref"> | Omit<ToggleGroupPrimitive.ToggleGroupMultipleProps & React.RefAttributes<HTMLDivElement>, "ref">) & VariantProps<(props?: ({
7
7
  variant?: "default" | "outline" | null | undefined;
8
- size?: "sm" | "lg" | "default" | "icon" | "icon-lg" | "avatar" | "chip" | null | undefined;
8
+ size?: "icon" | "sm" | "lg" | "default" | "icon-lg" | "avatar" | "chip" | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string>) & React.RefAttributes<HTMLDivElement>>;
10
10
  declare const ToggleGroupItem: React.ForwardRefExoticComponent<Omit<ToggleGroupPrimitive.ToggleGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & VariantProps<(props?: ({
11
11
  variant?: "default" | "outline" | null | undefined;
12
- size?: "sm" | "lg" | "default" | "icon" | "icon-lg" | "avatar" | "chip" | null | undefined;
12
+ size?: "icon" | "sm" | "lg" | "default" | "icon-lg" | "avatar" | "chip" | null | undefined;
13
13
  } & class_variance_authority_types.ClassProp) | undefined) => string> & {
14
14
  grow?: boolean;
15
15
  textSize?: "inherit" | "xs";
@@ -5,11 +5,11 @@ import * as React from 'react';
5
5
 
6
6
  declare const toggleVariants: (props?: ({
7
7
  variant?: "default" | "outline" | null | undefined;
8
- size?: "sm" | "lg" | "default" | "icon" | "icon-lg" | "avatar" | "chip" | null | undefined;
8
+ size?: "icon" | "sm" | "lg" | "default" | "icon-lg" | "avatar" | "chip" | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  declare const Toggle: React.ForwardRefExoticComponent<Omit<TogglePrimitive.ToggleProps & React.RefAttributes<HTMLButtonElement>, "ref"> & VariantProps<(props?: ({
11
11
  variant?: "default" | "outline" | null | undefined;
12
- size?: "sm" | "lg" | "default" | "icon" | "icon-lg" | "avatar" | "chip" | null | undefined;
12
+ size?: "icon" | "sm" | "lg" | "default" | "icon-lg" | "avatar" | "chip" | null | undefined;
13
13
  } & class_variance_authority_types.ClassProp) | undefined) => string> & React.RefAttributes<HTMLButtonElement>>;
14
14
 
15
15
  export { Toggle, toggleVariants };
@@ -5,11 +5,11 @@ import * as React from 'react';
5
5
 
6
6
  declare const toggleVariants: (props?: ({
7
7
  variant?: "default" | "outline" | null | undefined;
8
- size?: "sm" | "lg" | "default" | "icon" | "icon-lg" | "avatar" | "chip" | null | undefined;
8
+ size?: "icon" | "sm" | "lg" | "default" | "icon-lg" | "avatar" | "chip" | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  declare const Toggle: React.ForwardRefExoticComponent<Omit<TogglePrimitive.ToggleProps & React.RefAttributes<HTMLButtonElement>, "ref"> & VariantProps<(props?: ({
11
11
  variant?: "default" | "outline" | null | undefined;
12
- size?: "sm" | "lg" | "default" | "icon" | "icon-lg" | "avatar" | "chip" | null | undefined;
12
+ size?: "icon" | "sm" | "lg" | "default" | "icon-lg" | "avatar" | "chip" | null | undefined;
13
13
  } & class_variance_authority_types.ClassProp) | undefined) => string> & React.RefAttributes<HTMLButtonElement>>;
14
14
 
15
15
  export { Toggle, toggleVariants };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicbagel/saas-kit",
3
- "version": "1.6.0",
3
+ "version": "1.7.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": [