@epicbagel/saas-kit 1.29.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.
- package/dist/primitives/chat.cjs +10 -2
- package/dist/primitives/chat.d.cts +19 -5
- package/dist/primitives/chat.d.ts +19 -5
- package/dist/primitives/chat.js +10 -2
- package/dist/primitives/option-picker.cjs +74 -0
- package/dist/primitives/option-picker.d.cts +35 -0
- package/dist/primitives/option-picker.d.ts +35 -0
- package/dist/primitives/option-picker.js +74 -0
- package/package.json +11 -1
package/dist/primitives/chat.cjs
CHANGED
|
@@ -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",
|
|
@@ -1142,6 +1145,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
|
|
|
1142
1145
|
const workspace = layout === "workspace";
|
|
1143
1146
|
const shortViewport = useIsShortViewport();
|
|
1144
1147
|
const compactSuggestions = mobileCompactSuggestions && (isMobile || shortViewport);
|
|
1148
|
+
const compactComposer = mobileCompactComposer && (isMobile || shortViewport);
|
|
1145
1149
|
const showEmptyFileDropzone = emptyFileDropzone && Boolean(onFilesSelected) && attachments.length === 0;
|
|
1146
1150
|
const liveVoiceActive = voiceSession && voiceSession.state !== "disconnected" && voiceSession.state !== "error";
|
|
1147
1151
|
const showEmptyPending = !conversationStarted && pending && Boolean(emptyPendingContent) && !liveVoiceActive;
|
|
@@ -1261,6 +1265,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
|
|
|
1261
1265
|
uploadingLabel,
|
|
1262
1266
|
appearance: composerAppearance,
|
|
1263
1267
|
helperText: composerHelperText,
|
|
1268
|
+
tools: composerTools,
|
|
1264
1269
|
privacyNote: composerPrivacyNote,
|
|
1265
1270
|
className: workspace ? "rounded-3xl shadow-sm" : void 0
|
|
1266
1271
|
}
|
|
@@ -1327,7 +1332,9 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
|
|
|
1327
1332
|
"pt-3",
|
|
1328
1333
|
application && "px-4 py-3 md:bg-transparent md:px-0 md:pb-0 md:backdrop-blur-none md:supports-backdrop-filter:bg-transparent",
|
|
1329
1334
|
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",
|
|
1330
|
-
workspace && mobileCompactComposer && "max-md:pb-[max(0.75rem,env(safe-area-inset-bottom))] max-md:pt-3"
|
|
1335
|
+
workspace && mobileCompactComposer && "max-md:pb-[max(0.75rem,env(safe-area-inset-bottom))] max-md:pt-3",
|
|
1336
|
+
// A short window's dock is as tight as a phone's.
|
|
1337
|
+
workspace && compactComposer && "pb-[max(0.75rem,env(safe-area-inset-bottom))] pt-3"
|
|
1331
1338
|
),
|
|
1332
1339
|
children: [
|
|
1333
1340
|
!compactSuggestions && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
@@ -1375,8 +1382,9 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
|
|
|
1375
1382
|
addFilesLabel,
|
|
1376
1383
|
uploadingLabel,
|
|
1377
1384
|
appearance: composerAppearance,
|
|
1378
|
-
compactOnBlur:
|
|
1385
|
+
compactOnBlur: compactComposer,
|
|
1379
1386
|
helperText: composerHelperText,
|
|
1387
|
+
tools: composerTools,
|
|
1380
1388
|
privacyNote: composerPrivacyNote,
|
|
1381
1389
|
className: workspace ? "mx-auto max-w-3xl rounded-3xl shadow-sm" : void 0
|
|
1382
1390
|
}
|
|
@@ -232,13 +232,20 @@ 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
|
|
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. */
|
|
239
240
|
header?: ReactNode;
|
|
240
241
|
/** Quiet contextual copy between the composer controls. */
|
|
241
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;
|
|
242
249
|
/** Privacy or visibility reassurance rendered below the composer. */
|
|
243
250
|
privacyNote?: ReactNode;
|
|
244
251
|
}
|
|
@@ -259,7 +266,7 @@ interface ChatInputProps {
|
|
|
259
266
|
* `<Chat>` instead of composing `<ChatInput>` directly if you want
|
|
260
267
|
* device-aware default modes.
|
|
261
268
|
*/
|
|
262
|
-
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;
|
|
263
270
|
/** A next step offered in the empty state, under the composer (`emptyActions`). */
|
|
264
271
|
interface ChatEmptyAction {
|
|
265
272
|
/** Stable key. */
|
|
@@ -414,13 +421,20 @@ interface ChatProps {
|
|
|
414
421
|
/** Visual treatment for both empty and active text composers. */
|
|
415
422
|
composerAppearance?: ChatInputProps["appearance"];
|
|
416
423
|
/**
|
|
417
|
-
* On phones (below 768px)
|
|
418
|
-
* into a short text row above its
|
|
419
|
-
*
|
|
424
|
+
* On phones (below 768px wide) and short windows (below 900px tall), fold an
|
|
425
|
+
* active conversation's empty composer into a short text row above its
|
|
426
|
+
* controls until the textarea is focused. Drafts and files stay expanded.
|
|
427
|
+
* Taller desktop windows and the new-conversation composer are unchanged.
|
|
420
428
|
*/
|
|
421
429
|
mobileCompactComposer?: boolean;
|
|
422
430
|
/** Quiet contextual copy inside the composer action row. */
|
|
423
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;
|
|
424
438
|
/** Privacy or visibility reassurance below the composer. */
|
|
425
439
|
composerPrivacyNote?: ReactNode;
|
|
426
440
|
/** Show a prominent file drop target before the empty-state composer. */
|
|
@@ -232,13 +232,20 @@ 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
|
|
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. */
|
|
239
240
|
header?: ReactNode;
|
|
240
241
|
/** Quiet contextual copy between the composer controls. */
|
|
241
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;
|
|
242
249
|
/** Privacy or visibility reassurance rendered below the composer. */
|
|
243
250
|
privacyNote?: ReactNode;
|
|
244
251
|
}
|
|
@@ -259,7 +266,7 @@ interface ChatInputProps {
|
|
|
259
266
|
* `<Chat>` instead of composing `<ChatInput>` directly if you want
|
|
260
267
|
* device-aware default modes.
|
|
261
268
|
*/
|
|
262
|
-
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;
|
|
263
270
|
/** A next step offered in the empty state, under the composer (`emptyActions`). */
|
|
264
271
|
interface ChatEmptyAction {
|
|
265
272
|
/** Stable key. */
|
|
@@ -414,13 +421,20 @@ interface ChatProps {
|
|
|
414
421
|
/** Visual treatment for both empty and active text composers. */
|
|
415
422
|
composerAppearance?: ChatInputProps["appearance"];
|
|
416
423
|
/**
|
|
417
|
-
* On phones (below 768px)
|
|
418
|
-
* into a short text row above its
|
|
419
|
-
*
|
|
424
|
+
* On phones (below 768px wide) and short windows (below 900px tall), fold an
|
|
425
|
+
* active conversation's empty composer into a short text row above its
|
|
426
|
+
* controls until the textarea is focused. Drafts and files stay expanded.
|
|
427
|
+
* Taller desktop windows and the new-conversation composer are unchanged.
|
|
420
428
|
*/
|
|
421
429
|
mobileCompactComposer?: boolean;
|
|
422
430
|
/** Quiet contextual copy inside the composer action row. */
|
|
423
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;
|
|
424
438
|
/** Privacy or visibility reassurance below the composer. */
|
|
425
439
|
composerPrivacyNote?: ReactNode;
|
|
426
440
|
/** Show a prominent file drop target before the empty-state composer. */
|
package/dist/primitives/chat.js
CHANGED
|
@@ -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",
|
|
@@ -1142,6 +1145,7 @@ var Chat = forwardRef(function Chat2({
|
|
|
1142
1145
|
const workspace = layout === "workspace";
|
|
1143
1146
|
const shortViewport = useIsShortViewport();
|
|
1144
1147
|
const compactSuggestions = mobileCompactSuggestions && (isMobile || shortViewport);
|
|
1148
|
+
const compactComposer = mobileCompactComposer && (isMobile || shortViewport);
|
|
1145
1149
|
const showEmptyFileDropzone = emptyFileDropzone && Boolean(onFilesSelected) && attachments.length === 0;
|
|
1146
1150
|
const liveVoiceActive = voiceSession && voiceSession.state !== "disconnected" && voiceSession.state !== "error";
|
|
1147
1151
|
const showEmptyPending = !conversationStarted && pending && Boolean(emptyPendingContent) && !liveVoiceActive;
|
|
@@ -1261,6 +1265,7 @@ var Chat = forwardRef(function Chat2({
|
|
|
1261
1265
|
uploadingLabel,
|
|
1262
1266
|
appearance: composerAppearance,
|
|
1263
1267
|
helperText: composerHelperText,
|
|
1268
|
+
tools: composerTools,
|
|
1264
1269
|
privacyNote: composerPrivacyNote,
|
|
1265
1270
|
className: workspace ? "rounded-3xl shadow-sm" : void 0
|
|
1266
1271
|
}
|
|
@@ -1327,7 +1332,9 @@ var Chat = forwardRef(function Chat2({
|
|
|
1327
1332
|
"pt-3",
|
|
1328
1333
|
application && "px-4 py-3 md:bg-transparent md:px-0 md:pb-0 md:backdrop-blur-none md:supports-backdrop-filter:bg-transparent",
|
|
1329
1334
|
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",
|
|
1330
|
-
workspace && mobileCompactComposer && "max-md:pb-[max(0.75rem,env(safe-area-inset-bottom))] max-md:pt-3"
|
|
1335
|
+
workspace && mobileCompactComposer && "max-md:pb-[max(0.75rem,env(safe-area-inset-bottom))] max-md:pt-3",
|
|
1336
|
+
// A short window's dock is as tight as a phone's.
|
|
1337
|
+
workspace && compactComposer && "pb-[max(0.75rem,env(safe-area-inset-bottom))] pt-3"
|
|
1331
1338
|
),
|
|
1332
1339
|
children: [
|
|
1333
1340
|
!compactSuggestions && /* @__PURE__ */ jsx(
|
|
@@ -1375,8 +1382,9 @@ var Chat = forwardRef(function Chat2({
|
|
|
1375
1382
|
addFilesLabel,
|
|
1376
1383
|
uploadingLabel,
|
|
1377
1384
|
appearance: composerAppearance,
|
|
1378
|
-
compactOnBlur:
|
|
1385
|
+
compactOnBlur: compactComposer,
|
|
1379
1386
|
helperText: composerHelperText,
|
|
1387
|
+
tools: composerTools,
|
|
1380
1388
|
privacyNote: composerPrivacyNote,
|
|
1381
1389
|
className: workspace ? "mx-auto max-w-3xl rounded-3xl shadow-sm" : void 0
|
|
1382
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.
|
|
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",
|