@epicbagel/saas-kit 1.30.0 → 1.31.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.
@@ -458,6 +458,7 @@ function ChatInput({
458
458
  compactOnBlur = false,
459
459
  header,
460
460
  helperText,
461
+ tools,
461
462
  privacyNote
462
463
  }) {
463
464
  const internalRef = _react.useRef.call(void 0, null);
@@ -608,6 +609,7 @@ function ChatInput({
608
609
  addFilesLabel
609
610
  ] }) })
610
611
  ] }),
612
+ tools ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { "data-slot": "chat-input-tools", className: "flex shrink-0 items-center gap-1", children: tools }) : null,
611
613
  helperText ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
612
614
  "span",
613
615
  {
@@ -1048,6 +1050,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
1048
1050
  composerAppearance = "default",
1049
1051
  mobileCompactComposer = false,
1050
1052
  composerHelperText,
1053
+ composerTools,
1051
1054
  composerPrivacyNote,
1052
1055
  emptyFileDropzone = false,
1053
1056
  emptyFileDropzoneLabel = "Drop files here, or choose them",
@@ -1262,6 +1265,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
1262
1265
  uploadingLabel,
1263
1266
  appearance: composerAppearance,
1264
1267
  helperText: composerHelperText,
1268
+ tools: composerTools,
1265
1269
  privacyNote: composerPrivacyNote,
1266
1270
  className: workspace ? "rounded-3xl shadow-sm" : void 0
1267
1271
  }
@@ -1380,6 +1384,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
1380
1384
  appearance: composerAppearance,
1381
1385
  compactOnBlur: compactComposer,
1382
1386
  helperText: composerHelperText,
1387
+ tools: composerTools,
1383
1388
  privacyNote: composerPrivacyNote,
1384
1389
  className: workspace ? "mx-auto max-w-3xl rounded-3xl shadow-sm" : void 0
1385
1390
  }
@@ -240,6 +240,12 @@ interface ChatInputProps {
240
240
  header?: ReactNode;
241
241
  /** Quiet contextual copy between the composer controls. */
242
242
  helperText?: ReactNode;
243
+ /**
244
+ * Controls for the message, in the composer's control row after the `+`
245
+ * menu: a setting the next send uses, such as an `OptionPicker`. Each
246
+ * control keeps a 44px touch target; they stay in the folded composer too.
247
+ */
248
+ tools?: ReactNode;
243
249
  /** Privacy or visibility reassurance rendered below the composer. */
244
250
  privacyNote?: ReactNode;
245
251
  }
@@ -260,7 +266,7 @@ interface ChatInputProps {
260
266
  * `<Chat>` instead of composing `<ChatInput>` directly if you want
261
267
  * device-aware default modes.
262
268
  */
263
- declare function ChatInput({ value, onChange, onSend, pending, disabled, placeholder, size, uploadingLabel, voice, voiceSession, voiceTitle, voiceLang, submitLabel, autoFocus, className, inputRef, attachments, onFilesSelected, onRemoveAttachment, accept, multiple, addFilesLabel, appearance, compactOnBlur, header, helperText, privacyNote, }: ChatInputProps): react_jsx_runtime.JSX.Element;
269
+ declare function ChatInput({ value, onChange, onSend, pending, disabled, placeholder, size, uploadingLabel, voice, voiceSession, voiceTitle, voiceLang, submitLabel, autoFocus, className, inputRef, attachments, onFilesSelected, onRemoveAttachment, accept, multiple, addFilesLabel, appearance, compactOnBlur, header, helperText, tools, privacyNote, }: ChatInputProps): react_jsx_runtime.JSX.Element;
264
270
  /** A next step offered in the empty state, under the composer (`emptyActions`). */
265
271
  interface ChatEmptyAction {
266
272
  /** Stable key. */
@@ -423,6 +429,12 @@ interface ChatProps {
423
429
  mobileCompactComposer?: boolean;
424
430
  /** Quiet contextual copy inside the composer action row. */
425
431
  composerHelperText?: ReactNode;
432
+ /**
433
+ * Controls in the composer's control row, after the `+` menu, on the new
434
+ * conversation's composer and the conversation's alike: a setting the next
435
+ * message uses, such as an `OptionPicker`.
436
+ */
437
+ composerTools?: ReactNode;
426
438
  /** Privacy or visibility reassurance below the composer. */
427
439
  composerPrivacyNote?: ReactNode;
428
440
  /** Show a prominent file drop target before the empty-state composer. */
@@ -240,6 +240,12 @@ interface ChatInputProps {
240
240
  header?: ReactNode;
241
241
  /** Quiet contextual copy between the composer controls. */
242
242
  helperText?: ReactNode;
243
+ /**
244
+ * Controls for the message, in the composer's control row after the `+`
245
+ * menu: a setting the next send uses, such as an `OptionPicker`. Each
246
+ * control keeps a 44px touch target; they stay in the folded composer too.
247
+ */
248
+ tools?: ReactNode;
243
249
  /** Privacy or visibility reassurance rendered below the composer. */
244
250
  privacyNote?: ReactNode;
245
251
  }
@@ -260,7 +266,7 @@ interface ChatInputProps {
260
266
  * `<Chat>` instead of composing `<ChatInput>` directly if you want
261
267
  * device-aware default modes.
262
268
  */
263
- declare function ChatInput({ value, onChange, onSend, pending, disabled, placeholder, size, uploadingLabel, voice, voiceSession, voiceTitle, voiceLang, submitLabel, autoFocus, className, inputRef, attachments, onFilesSelected, onRemoveAttachment, accept, multiple, addFilesLabel, appearance, compactOnBlur, header, helperText, privacyNote, }: ChatInputProps): react_jsx_runtime.JSX.Element;
269
+ declare function ChatInput({ value, onChange, onSend, pending, disabled, placeholder, size, uploadingLabel, voice, voiceSession, voiceTitle, voiceLang, submitLabel, autoFocus, className, inputRef, attachments, onFilesSelected, onRemoveAttachment, accept, multiple, addFilesLabel, appearance, compactOnBlur, header, helperText, tools, privacyNote, }: ChatInputProps): react_jsx_runtime.JSX.Element;
264
270
  /** A next step offered in the empty state, under the composer (`emptyActions`). */
265
271
  interface ChatEmptyAction {
266
272
  /** Stable key. */
@@ -423,6 +429,12 @@ interface ChatProps {
423
429
  mobileCompactComposer?: boolean;
424
430
  /** Quiet contextual copy inside the composer action row. */
425
431
  composerHelperText?: ReactNode;
432
+ /**
433
+ * Controls in the composer's control row, after the `+` menu, on the new
434
+ * conversation's composer and the conversation's alike: a setting the next
435
+ * message uses, such as an `OptionPicker`.
436
+ */
437
+ composerTools?: ReactNode;
426
438
  /** Privacy or visibility reassurance below the composer. */
427
439
  composerPrivacyNote?: ReactNode;
428
440
  /** Show a prominent file drop target before the empty-state composer. */
@@ -458,6 +458,7 @@ function ChatInput({
458
458
  compactOnBlur = false,
459
459
  header,
460
460
  helperText,
461
+ tools,
461
462
  privacyNote
462
463
  }) {
463
464
  const internalRef = useRef(null);
@@ -608,6 +609,7 @@ function ChatInput({
608
609
  addFilesLabel
609
610
  ] }) })
610
611
  ] }),
612
+ tools ? /* @__PURE__ */ jsx("div", { "data-slot": "chat-input-tools", className: "flex shrink-0 items-center gap-1", children: tools }) : null,
611
613
  helperText ? /* @__PURE__ */ jsx(
612
614
  "span",
613
615
  {
@@ -1048,6 +1050,7 @@ var Chat = forwardRef(function Chat2({
1048
1050
  composerAppearance = "default",
1049
1051
  mobileCompactComposer = false,
1050
1052
  composerHelperText,
1053
+ composerTools,
1051
1054
  composerPrivacyNote,
1052
1055
  emptyFileDropzone = false,
1053
1056
  emptyFileDropzoneLabel = "Drop files here, or choose them",
@@ -1262,6 +1265,7 @@ var Chat = forwardRef(function Chat2({
1262
1265
  uploadingLabel,
1263
1266
  appearance: composerAppearance,
1264
1267
  helperText: composerHelperText,
1268
+ tools: composerTools,
1265
1269
  privacyNote: composerPrivacyNote,
1266
1270
  className: workspace ? "rounded-3xl shadow-sm" : void 0
1267
1271
  }
@@ -1380,6 +1384,7 @@ var Chat = forwardRef(function Chat2({
1380
1384
  appearance: composerAppearance,
1381
1385
  compactOnBlur: compactComposer,
1382
1386
  helperText: composerHelperText,
1387
+ tools: composerTools,
1383
1388
  privacyNote: composerPrivacyNote,
1384
1389
  className: workspace ? "mx-auto max-w-3xl rounded-3xl shadow-sm" : void 0
1385
1390
  }
@@ -0,0 +1,74 @@
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
+ "use client";
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+ var _chunkZAITUDYDcjs = require('../chunk-ZAITUDYD.cjs');
11
+
12
+
13
+ var _chunkJDEXZGIYcjs = require('../chunk-JDEXZGIY.cjs');
14
+
15
+
16
+ var _chunkKWLSBTB2cjs = require('../chunk-KWLSBTB2.cjs');
17
+
18
+ // src/primitives/option-picker.tsx
19
+ var _lucidereact = require('lucide-react');
20
+ var _react = require('react');
21
+ var _jsxruntime = require('react/jsx-runtime');
22
+ function OptionPicker({
23
+ value,
24
+ onValueChange,
25
+ options,
26
+ label,
27
+ icon,
28
+ disabled = false,
29
+ side = "top",
30
+ align = "start",
31
+ className
32
+ }) {
33
+ const headingId = _react.useId.call(void 0, );
34
+ const current = options.find((option) => option.value === value);
35
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkZAITUDYDcjs.DropdownMenu, { children: [
36
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkZAITUDYDcjs.DropdownMenuTrigger, { asChild: true, disabled, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
37
+ _chunkJDEXZGIYcjs.Button,
38
+ {
39
+ type: "button",
40
+ variant: "ghost",
41
+ "aria-label": `${label}: ${_nullishCoalesce(_optionalChain([current, 'optionalAccess', _ => _.label]), () => ( value))}`,
42
+ "data-slot": "option-picker-trigger",
43
+ className: _chunkKWLSBTB2cjs.cn.call(void 0, "min-h-[44px] shrink-0 gap-1.5 px-3 text-sm font-medium", className),
44
+ children: [
45
+ icon,
46
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: _nullishCoalesce(_optionalChain([current, 'optionalAccess', _2 => _2.label]), () => ( value)) }),
47
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.ChevronUp, { "aria-hidden": true, className: _chunkKWLSBTB2cjs.cn.call(void 0, "h-3.5 w-3.5 opacity-60", side === "bottom" && "rotate-180") })
48
+ ]
49
+ }
50
+ ) }),
51
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkZAITUDYDcjs.DropdownMenuContent, { side, align, className: "w-80 max-w-[calc(100vw-2rem)]", "aria-labelledby": headingId, children: [
52
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkZAITUDYDcjs.DropdownMenuLabel, { id: headingId, className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: label }),
53
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkZAITUDYDcjs.DropdownMenuRadioGroup, { value, onValueChange: (next) => onValueChange(next), children: options.map((option) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
54
+ _chunkZAITUDYDcjs.DropdownMenuRadioItem,
55
+ {
56
+ value: option.value,
57
+ "data-slot": "option-picker-option",
58
+ className: "items-start py-2.5 sm:py-2.5 [&>span:first-child]:top-3.5",
59
+ children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "flex min-w-0 flex-col gap-0.5", children: [
60
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "font-medium", children: [
61
+ option.label,
62
+ option.tag ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "ml-1.5 text-xs font-normal text-muted-foreground", children: option.tag }) : null
63
+ ] }),
64
+ option.description ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-xs leading-relaxed text-muted-foreground", children: option.description }) : null
65
+ ] })
66
+ },
67
+ option.value
68
+ )) })
69
+ ] })
70
+ ] });
71
+ }
72
+
73
+
74
+ exports.OptionPicker = OptionPicker;
@@ -0,0 +1,35 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface OptionPickerOption<TValue extends string = string> {
5
+ value: TValue;
6
+ /** The option's name, also shown on the button once picked. */
7
+ label: string;
8
+ /** One line on what picking it means. */
9
+ description?: ReactNode;
10
+ /** A quiet note after the name, such as "default". */
11
+ tag?: ReactNode;
12
+ }
13
+ interface OptionPickerProps<TValue extends string = string> {
14
+ value: TValue;
15
+ onValueChange: (value: TValue) => void;
16
+ options: ReadonlyArray<OptionPickerOption<TValue>>;
17
+ /** Names the setting: the list's heading, and the button's accessible name with the value. */
18
+ label: string;
19
+ /** Shown before the value on the button. */
20
+ icon?: ReactNode;
21
+ disabled?: boolean;
22
+ /** Where the list opens. Defaults to `top`, for a control in a composer. */
23
+ side?: "top" | "bottom";
24
+ align?: "start" | "end";
25
+ className?: string;
26
+ }
27
+ /**
28
+ * A setting picked from a short list, the way a chat composer picks its effort:
29
+ * a button showing the current value opens a list of options, each a name and
30
+ * one line on what it means, one of them picked. A controlled radio group in a
31
+ * menu, so arrow keys move between options and Enter picks one.
32
+ */
33
+ declare function OptionPicker<TValue extends string = string>({ value, onValueChange, options, label, icon, disabled, side, align, className, }: OptionPickerProps<TValue>): react_jsx_runtime.JSX.Element;
34
+
35
+ export { OptionPicker, type OptionPickerOption, type OptionPickerProps };
@@ -0,0 +1,35 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ interface OptionPickerOption<TValue extends string = string> {
5
+ value: TValue;
6
+ /** The option's name, also shown on the button once picked. */
7
+ label: string;
8
+ /** One line on what picking it means. */
9
+ description?: ReactNode;
10
+ /** A quiet note after the name, such as "default". */
11
+ tag?: ReactNode;
12
+ }
13
+ interface OptionPickerProps<TValue extends string = string> {
14
+ value: TValue;
15
+ onValueChange: (value: TValue) => void;
16
+ options: ReadonlyArray<OptionPickerOption<TValue>>;
17
+ /** Names the setting: the list's heading, and the button's accessible name with the value. */
18
+ label: string;
19
+ /** Shown before the value on the button. */
20
+ icon?: ReactNode;
21
+ disabled?: boolean;
22
+ /** Where the list opens. Defaults to `top`, for a control in a composer. */
23
+ side?: "top" | "bottom";
24
+ align?: "start" | "end";
25
+ className?: string;
26
+ }
27
+ /**
28
+ * A setting picked from a short list, the way a chat composer picks its effort:
29
+ * a button showing the current value opens a list of options, each a name and
30
+ * one line on what it means, one of them picked. A controlled radio group in a
31
+ * menu, so arrow keys move between options and Enter picks one.
32
+ */
33
+ declare function OptionPicker<TValue extends string = string>({ value, onValueChange, options, label, icon, disabled, side, align, className, }: OptionPickerProps<TValue>): react_jsx_runtime.JSX.Element;
34
+
35
+ export { OptionPicker, type OptionPickerOption, type OptionPickerProps };
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ "use client";
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuLabel,
7
+ DropdownMenuRadioGroup,
8
+ DropdownMenuRadioItem,
9
+ DropdownMenuTrigger
10
+ } from "../chunk-V4LE2HWP.js";
11
+ import {
12
+ Button
13
+ } from "../chunk-W326GKML.js";
14
+ import {
15
+ cn
16
+ } from "../chunk-G6SRSWXB.js";
17
+
18
+ // src/primitives/option-picker.tsx
19
+ import { ChevronUp } from "lucide-react";
20
+ import { useId } from "react";
21
+ import { jsx, jsxs } from "react/jsx-runtime";
22
+ function OptionPicker({
23
+ value,
24
+ onValueChange,
25
+ options,
26
+ label,
27
+ icon,
28
+ disabled = false,
29
+ side = "top",
30
+ align = "start",
31
+ className
32
+ }) {
33
+ const headingId = useId();
34
+ const current = options.find((option) => option.value === value);
35
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
36
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxs(
37
+ Button,
38
+ {
39
+ type: "button",
40
+ variant: "ghost",
41
+ "aria-label": `${label}: ${current?.label ?? value}`,
42
+ "data-slot": "option-picker-trigger",
43
+ className: cn("min-h-[44px] shrink-0 gap-1.5 px-3 text-sm font-medium", className),
44
+ children: [
45
+ icon,
46
+ /* @__PURE__ */ jsx("span", { children: current?.label ?? value }),
47
+ /* @__PURE__ */ jsx(ChevronUp, { "aria-hidden": true, className: cn("h-3.5 w-3.5 opacity-60", side === "bottom" && "rotate-180") })
48
+ ]
49
+ }
50
+ ) }),
51
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { side, align, className: "w-80 max-w-[calc(100vw-2rem)]", "aria-labelledby": headingId, children: [
52
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { id: headingId, className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: label }),
53
+ /* @__PURE__ */ jsx(DropdownMenuRadioGroup, { value, onValueChange: (next) => onValueChange(next), children: options.map((option) => /* @__PURE__ */ jsx(
54
+ DropdownMenuRadioItem,
55
+ {
56
+ value: option.value,
57
+ "data-slot": "option-picker-option",
58
+ className: "items-start py-2.5 sm:py-2.5 [&>span:first-child]:top-3.5",
59
+ children: /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
60
+ /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
61
+ option.label,
62
+ option.tag ? /* @__PURE__ */ jsx("span", { className: "ml-1.5 text-xs font-normal text-muted-foreground", children: option.tag }) : null
63
+ ] }),
64
+ option.description ? /* @__PURE__ */ jsx("span", { className: "text-xs leading-relaxed text-muted-foreground", children: option.description }) : null
65
+ ] })
66
+ },
67
+ option.value
68
+ )) })
69
+ ] })
70
+ ] });
71
+ }
72
+ export {
73
+ OptionPicker
74
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicbagel/saas-kit",
3
- "version": "1.30.0",
3
+ "version": "1.31.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": [
@@ -790,6 +790,16 @@
790
790
  "default": "./dist/primitives/notes.cjs"
791
791
  }
792
792
  },
793
+ "./primitives/option-picker": {
794
+ "import": {
795
+ "types": "./dist/primitives/option-picker.d.ts",
796
+ "default": "./dist/primitives/option-picker.js"
797
+ },
798
+ "require": {
799
+ "types": "./dist/primitives/option-picker.d.cts",
800
+ "default": "./dist/primitives/option-picker.cjs"
801
+ }
802
+ },
793
803
  "./primitives/page-header-actions": {
794
804
  "import": {
795
805
  "types": "./dist/primitives/page-header-actions.d.ts",