# ReplySuggestions

> The band between a Unibox thread and its composer: ask for a few replies, watch them arrive, pick one.

## Facts

- **Product**: Max
- **Family**: Unibox
- **Global**: `window.DigitalCrew.ReplySuggestions`
- **Source**: `max-agent: src/features/unibox/ui/components/reply-suggestions.tsx`
- **Import in the app**: `import { ReplySuggestions } from "@/features/unibox/ui/components/reply-suggestions";`
- **Live preview**: /design/components/reply-suggestions/preview.html
- **Page**: /design/components/reply-suggestions

## Guidelines

A pick fills the composer and is never sent for you.

**Use it for** suggesting the next message in a conversation, aimed at the workspace's deal stages.

**What you provide**
- State: `suggestions`, `isLoading`, `error` and `hasGenerated`.
  - Each suggestion is `{ id, label, intent, deal_target, rationale, text }`.
  - `intent` is qualify, value, meeting, proposal, pricing, objection, close or nurture, and picks the icon.
- `usedProspectCard` and `dealTypeCount` set the source note, such as "From their contact card and your 2 deal types".
- Callbacks: `onGenerate`, `onDismiss` and `onSelect(text)`.
- Optional: `composerValue` (marks the card in play), `disabled` and `className`.

**Anatomy**
- The trigger pill is `rounded-full border px-3 py-1.5 text-xs`. It is tinted primary before a run ("Suggest replies"), neutral after it ("Suggest again"), and reads "Writing options…" while loading.
- Cards sit in 1, 2 (`sm`) or 3 (`lg`) columns. Each is `rounded-xl border-border/60 bg-background/70 p-3 shadow-sm`, with an intent icon, a `text-xs font-semibold` label, the deal target, three clamped lines and an italic rationale.
- Hover gives `border-primary/40`. The card in play gets `border-primary/60 bg-primary/[0.07]` and a check.
- While loading, three pulsing `h-24` skeletons stand in.

**Motion**: panels swap on `FADE_SWAP` (0.16s). Cards rise 8px on `SPRING_ENTER`, staggered 0.05s, except under reduced motion.

**Don't** auto-send a suggestion or hide its source. The last word stays with the person whose name is on the account.

## API

```ts
interface ReplySuggestionsProps {
    suggestions: ReplySuggestion[];
    isLoading: boolean;
    error: string | null;
    /** A run finished for this conversation (even if it came back empty). */
    hasGenerated: boolean;
    /** The contact card fed the last run, not just the thread. */
    usedProspectCard: boolean;
    /** How many deal types the options could aim at. */
    dealTypeCount: number;
    onGenerate: () => void;
    onDismiss: () => void;
    /** Put this text in the composer, ready to edit and send. */
    onSelect: (text: string) => void;
    /**
     * What the composer holds right now. A card matching it is marked as the
     * one in play — and stops matching the moment the user edits a word, which
     * is exactly when it stops being that suggestion.
     */
    composerValue?: string;
    /** Sending locks the panel: the reply is already on its way. */
    disabled?: boolean;
    className?: string;
}
export declare function ReplySuggestions({ suggestions, isLoading, error, hasGenerated, usedProspectCard, dealTypeCount, onGenerate, onDismiss, onSelect, composerValue, disabled, className, }: ReplySuggestionsProps): JSX.Element;
```

## Example

```html
<div id="root" class="p-6"></div>
<script>
(function () {
  var h = React.createElement, DC = window.DigitalCrew, I = DC.Icons;
  var SUGGESTIONS = [
    { id: "s1", label: "Book the call", intent: "meeting", deal_target: "New business → Meeting booked",
      rationale: "She named a day; lock it before the week fills.",
      text: "Great, Thursday at 10:00 CET it is. I'll send the invite to you and Anouk now, with a 20-minute agenda: your current close process, where the two days go, and what a pilot would look like." },
    { id: "s2", label: "Share the ROI case", intent: "value", deal_target: "New business → Qualified",
      rationale: "A CFO buys on numbers; give her one to bring.",
      text: "Before Thursday, here is how Brightline Logistics cut month-end close from six days to three with us. One page, numbers included. Happy to walk through what the same model would mean for Northwind." },
    { id: "s3", label: "Qualify the buying group", intent: "qualify", deal_target: "New business → Discovery",
      rationale: "A Q1 review means the budget is being set now.",
      text: "Thanks Claire. So I bring the right material: is the Q1 tooling review owned by finance alone, or will IT and procurement join Thursday's call too?" }
  ];
  function App() {
    var sg = React.useState(SUGGESTIONS), list = sg[0], setList = sg[1];
    var ld = React.useState(false), loading = ld[0], setLoading = ld[1];
    var gen = React.useState(true), generated = gen[0], setGenerated = gen[1];
    var cv = React.useState(SUGGESTIONS[0].text), value = cv[0], setValue = cv[1];
    return h("div", { className: "mx-auto flex max-w-[900px] flex-col gap-3" },
      h(DC.ReplySuggestions, {
        suggestions: list, isLoading: loading, error: null, hasGenerated: generated,
        usedProspectCard: true, dealTypeCount: 2, composerValue: value,
        onGenerate: function () {
          setLoading(true); setList([]);
          setTimeout(function () { setLoading(false); setGenerated(true); setList(SUGGESTIONS); }, 1600);
        },
        onDismiss: function () { setList([]); setGenerated(false); },
        onSelect: function (text) { setValue(text); }
      }),
      h(DC.MessageComposer, {
        value: value, onChange: setValue, attachments: [], isSending: false,
        onAddFiles: function () {}, onRemoveAttachment: function () {}, onSubmit: function () {}
      }));
  }
  ReactDOM.createRoot(document.getElementById("root")).render(h(DC.TooltipProvider, { delayDuration: 80 }, h(App)));
})();
</script>
```

## More in Unibox

- [ChannelSegments](/design/components/channel-segments.md): The Unibox channel filter: a rail of icons where one shared pill glides to the selected channel, and only that segment spells out its name.
- [ChatListItem](/design/components/chat-list-item.md): One conversation in the Unibox list: a motion row that staggers in, glides when a new reply reorders the inbox, and pops its unread badge.
- [MessageBubble](/design/components/message-bubble.md): One message in a Unibox thread: your side in the brand gradient, theirs in muted glass, each rising in from its own side of the conversation.
- [MessageComposer](/design/components/message-composer.md): The Unibox reply box: text, emoji and staged images in one rounded field, with a send button that springs awake when there is something to send.
- [SyncProgressPanel](/design/components/sync-progress-panel.md): The dialog behind the Unibox sync button.
- [UniboxIdlePanel](/design/components/unibox-idle-panel.md): The Unibox's empty conversation pane: a breathing glass orb with the three channels in slow orbit and a quiet "Listening for new replies" line.
