prompt-area 0.1.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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -0
  3. package/dist/action-bar/index.d.ts +60 -0
  4. package/dist/action-bar/index.js +5 -0
  5. package/dist/action-bar/index.js.map +1 -0
  6. package/dist/chat-prompt-layout/index.d.ts +53 -0
  7. package/dist/chat-prompt-layout/index.js +5 -0
  8. package/dist/chat-prompt-layout/index.js.map +1 -0
  9. package/dist/chunk-ANZZEZP2.js +38 -0
  10. package/dist/chunk-ANZZEZP2.js.map +1 -0
  11. package/dist/chunk-BPJO4DGM.js +198 -0
  12. package/dist/chunk-BPJO4DGM.js.map +1 -0
  13. package/dist/chunk-BWVBDP7C.js +38 -0
  14. package/dist/chunk-BWVBDP7C.js.map +1 -0
  15. package/dist/chunk-E7HUXORB.js +2692 -0
  16. package/dist/chunk-E7HUXORB.js.map +1 -0
  17. package/dist/chunk-NF2LHZIE.js +12 -0
  18. package/dist/chunk-NF2LHZIE.js.map +1 -0
  19. package/dist/chunk-UBBCAMJA.js +116 -0
  20. package/dist/chunk-UBBCAMJA.js.map +1 -0
  21. package/dist/chunk-XDKRP7UE.js +125 -0
  22. package/dist/chunk-XDKRP7UE.js.map +1 -0
  23. package/dist/compact-prompt-area/index.d.ts +86 -0
  24. package/dist/compact-prompt-area/index.js +6 -0
  25. package/dist/compact-prompt-area/index.js.map +1 -0
  26. package/dist/helpers/index.d.ts +374 -0
  27. package/dist/helpers/index.js +291 -0
  28. package/dist/helpers/index.js.map +1 -0
  29. package/dist/index.d.ts +7 -0
  30. package/dist/index.js +10 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/prompt-area/index.d.ts +327 -0
  33. package/dist/prompt-area/index.js +6 -0
  34. package/dist/prompt-area/index.js.map +1 -0
  35. package/dist/status-bar/index.d.ts +50 -0
  36. package/dist/status-bar/index.js +5 -0
  37. package/dist/status-bar/index.js.map +1 -0
  38. package/dist/styles.css +2 -0
  39. package/dist/tailwind.css +181 -0
  40. package/dist/types-C4BgDEpe.d.ts +271 -0
  41. package/package.json +102 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ilko Kacharov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # prompt-area
2
+
3
+ An opinionated, dependency-light React rich-text **prompt input** — trigger-based chips (`@mentions`, `/commands`, `#tags`), inline markdown, undo/redo, file & image attachments, and a complete chat-input layout.
4
+
5
+ Ships **two ways** from the same source:
6
+
7
+ - **npm package** (this package) — `npm install prompt-area`, import the component and a stylesheet. Versioned, opinionated, batteries-included.
8
+ - **[shadcn registry](https://prompt-area.com)** — `npx shadcn@latest add https://prompt-area.com/r/prompt-area.json` to copy the source into your project and own it.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install prompt-area
14
+ # peer deps (most React apps already have these)
15
+ npm install react react-dom
16
+ ```
17
+
18
+ ## Quick start
19
+
20
+ ```tsx
21
+ 'use client'
22
+ import { useState } from 'react'
23
+ import { PromptArea, mentionTrigger, type Segment } from 'prompt-area'
24
+ import 'prompt-area/styles.css'
25
+
26
+ export function Composer() {
27
+ const [value, setValue] = useState<Segment[]>([])
28
+
29
+ return (
30
+ <PromptArea
31
+ value={value}
32
+ onChange={setValue}
33
+ placeholder="Type a message…"
34
+ autoGrow
35
+ triggers={[
36
+ mentionTrigger({
37
+ onSearch: async (q) =>
38
+ [
39
+ { id: '1', label: 'Ada Lovelace', value: 'ada' },
40
+ { id: '2', label: 'Alan Turing', value: 'alan' },
41
+ ].filter((u) => u.label.toLowerCase().includes(q.toLowerCase())),
42
+ }),
43
+ ]}
44
+ onSubmit={(segments) => console.log(segments)}
45
+ />
46
+ )
47
+ }
48
+ ```
49
+
50
+ ## Styling
51
+
52
+ The components are styled with Tailwind utility classes plus a small set of design tokens. You can use them **with or without** Tailwind.
53
+
54
+ ### Option A — prebuilt CSS (zero config)
55
+
56
+ Import the compiled stylesheet once. It is self-contained, ships **no global reset** (it won't restyle the rest of your app), and works in any React app.
57
+
58
+ ```ts
59
+ import 'prompt-area/styles.css'
60
+ ```
61
+
62
+ Theme it by overriding the CSS variables (same names as shadcn/ui):
63
+
64
+ ```css
65
+ :root {
66
+ --primary: oklch(0.62 0.19 260);
67
+ --radius: 0.5rem;
68
+ }
69
+ ```
70
+
71
+ Dark mode follows a `.dark` ancestor (add `class="dark"` to `<html>` or a wrapper).
72
+
73
+ ### Option B — Tailwind v4 preset (themeable)
74
+
75
+ If you run Tailwind yourself, import the preset from your CSS entry instead. It registers the tokens and component styles and points Tailwind at the built files so the utilities are generated in your pipeline.
76
+
77
+ ```css
78
+ @import 'tailwindcss';
79
+ @import 'prompt-area/tailwind.css';
80
+ /* for the entrance/spinner animations: */
81
+ @import 'tw-animate-css';
82
+ ```
83
+
84
+ In a shadcn project the token names already exist, so the component automatically inherits your theme — you usually don't need to do anything.
85
+
86
+ ## Exports
87
+
88
+ | Import | Description |
89
+ | --------------------------------- | --------------------------------------------------------- |
90
+ | `prompt-area` | Everything: components, hooks, helpers, and types |
91
+ | `prompt-area/prompt-area` | `PromptArea` + its types/hooks |
92
+ | `prompt-area/action-bar` | `ActionBar` |
93
+ | `prompt-area/status-bar` | `StatusBar` |
94
+ | `prompt-area/compact-prompt-area` | `CompactPromptArea` |
95
+ | `prompt-area/chat-prompt-layout` | `ChatPromptLayout` |
96
+ | `prompt-area/helpers` | Framework-agnostic helpers, **safe in Server Components** |
97
+ | `prompt-area/styles.css` | Prebuilt stylesheet |
98
+ | `prompt-area/tailwind.css` | Tailwind v4 preset |
99
+
100
+ All blocks are tree-shakeable, so deep-importing a single block keeps your bundle small.
101
+
102
+ ### React Server Components
103
+
104
+ The components are client components and carry a `'use client'` boundary, so you can render them directly from a Server Component. The pure helpers (e.g. `segmentsToPlainText`, `parseInlineMarkdown`, trigger presets) are also re-exported from `prompt-area/helpers` **without** the client boundary, so they can run on the server too.
105
+
106
+ ## Dependencies
107
+
108
+ - **Peer:** `react`, `react-dom` (>= 18)
109
+ - **Runtime:** `clsx`, `tailwind-merge` — the two `cn` helpers, both already
110
+ present in any shadcn/Tailwind project.
111
+
112
+ No animation library and no icon library: animations use CSS (`tw-animate-css`
113
+ utilities) and icons are inline SVGs. No editor framework (ProseMirror, Slate,
114
+ Lexical) either.
115
+
116
+ ESM-only. Requires Node 18+ / a modern bundler (Next.js, Vite, etc.).
117
+
118
+ ## License
119
+
120
+ MIT
@@ -0,0 +1,60 @@
1
+ import * as react from 'react';
2
+
3
+ /**
4
+ * ActionBar component types
5
+ *
6
+ * A horizontal toolbar with left and right slots, designed to sit
7
+ * below a text input (e.g., PromptArea) and stay anchored via
8
+ * normal document flow.
9
+ */
10
+ /**
11
+ * Props for the ActionBar component.
12
+ */
13
+ type ActionBarProps = {
14
+ /** Content rendered on the left side of the bar */
15
+ left?: React.ReactNode;
16
+ /** Content rendered on the right side of the bar */
17
+ right?: React.ReactNode;
18
+ /** Additional CSS class for the root element */
19
+ className?: string;
20
+ /** Whether the action bar is disabled (visually dims and disables pointer events) */
21
+ disabled?: boolean;
22
+ /** Accessible label for the toolbar */
23
+ 'aria-label'?: string;
24
+ /** data-test-id for e2e testing */
25
+ 'data-test-id'?: string;
26
+ };
27
+
28
+ /**
29
+ * ActionBar - A horizontal toolbar with left and right slots.
30
+ *
31
+ * Designed to sit below a text input (e.g., PromptArea) and stay
32
+ * anchored via normal document flow. Place it as a sibling after
33
+ * the input inside a shared wrapper.
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * <div className="rounded-lg border p-4">
38
+ * <PromptArea value={segments} onChange={setSegments} ... />
39
+ * <ActionBar
40
+ * left={
41
+ * <>
42
+ * <button><PlusCircle /></button>
43
+ * <button><AtSign /></button>
44
+ * </>
45
+ * }
46
+ * right={
47
+ * <>
48
+ * <button><Mic /></button>
49
+ * <button onClick={handleSubmit}><ArrowUp /></button>
50
+ * </>
51
+ * }
52
+ * />
53
+ * </div>
54
+ * ```
55
+ */
56
+ declare function ActionBar({ left, right, className, disabled, 'aria-label': ariaLabel, 'data-test-id': dataTestId, ref, }: ActionBarProps & {
57
+ ref?: React.Ref<HTMLDivElement>;
58
+ }): react.JSX.Element;
59
+
60
+ export { ActionBar, type ActionBarProps };
@@ -0,0 +1,5 @@
1
+ 'use client';
2
+ export { ActionBar } from '../chunk-BWVBDP7C.js';
3
+ import '../chunk-NF2LHZIE.js';
4
+ //# sourceMappingURL=index.js.map
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,53 @@
1
+ import * as react from 'react';
2
+
3
+ /**
4
+ * ChatPromptLayout component types
5
+ *
6
+ * A full-height chat layout with a scrollable messages area and
7
+ * a bottom-anchored prompt slot. Includes contextual scroll
8
+ * navigation buttons.
9
+ */
10
+ /**
11
+ * Props for the ChatPromptLayout component.
12
+ */
13
+ type ChatPromptLayoutProps = {
14
+ /** Chat messages rendered in the scrollable area */
15
+ children: React.ReactNode;
16
+ /** Prompt area rendered at the bottom of the layout (slot) */
17
+ prompt: React.ReactNode;
18
+ /** Additional CSS class for the root container */
19
+ className?: string;
20
+ /** Accessible label for the layout region */
21
+ 'aria-label'?: string;
22
+ /** data-test-id for e2e testing */
23
+ 'data-test-id'?: string;
24
+ };
25
+
26
+ /**
27
+ * ChatPromptLayout - A full-height chat layout with scrollable messages
28
+ * and a bottom-anchored prompt slot.
29
+ *
30
+ * Pass chat messages as `children` and the prompt area via the `prompt`
31
+ * prop. Contextual scroll buttons appear when the user scrolls away
32
+ * from the top or bottom of the messages area.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * <ChatPromptLayout
37
+ * className="h-[600px]"
38
+ * prompt={
39
+ * <div className="border-t p-4">
40
+ * <PromptArea ... />
41
+ * <ActionBar ... />
42
+ * </div>
43
+ * }
44
+ * >
45
+ * {messages.map(msg => <ChatBubble key={msg.id} {...msg} />)}
46
+ * </ChatPromptLayout>
47
+ * ```
48
+ */
49
+ declare function ChatPromptLayout({ children, prompt, className, 'aria-label': ariaLabel, 'data-test-id': dataTestId, ref, }: ChatPromptLayoutProps & {
50
+ ref?: React.Ref<HTMLDivElement>;
51
+ }): react.JSX.Element;
52
+
53
+ export { ChatPromptLayout, type ChatPromptLayoutProps };
@@ -0,0 +1,5 @@
1
+ 'use client';
2
+ export { ChatPromptLayout } from '../chunk-XDKRP7UE.js';
3
+ import '../chunk-NF2LHZIE.js';
4
+ //# sourceMappingURL=index.js.map
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+ import { cn } from './chunk-NF2LHZIE.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ function StatusBar({
6
+ left,
7
+ right,
8
+ className,
9
+ disabled = false,
10
+ "aria-label": ariaLabel,
11
+ "data-test-id": dataTestId,
12
+ ref
13
+ }) {
14
+ return /* @__PURE__ */ jsxs(
15
+ "div",
16
+ {
17
+ ref,
18
+ role: "group",
19
+ "aria-label": ariaLabel ?? "Status bar",
20
+ "aria-disabled": disabled || void 0,
21
+ "data-test-id": dataTestId,
22
+ className: cn(
23
+ "status-bar",
24
+ "flex items-center justify-between gap-2 px-3 py-1.5 text-xs",
25
+ disabled && "pointer-events-none opacity-50",
26
+ className
27
+ ),
28
+ children: [
29
+ left && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: left }),
30
+ right && /* @__PURE__ */ jsx("div", { className: "ml-auto flex items-center gap-1.5", children: right })
31
+ ]
32
+ }
33
+ );
34
+ }
35
+
36
+ export { StatusBar };
37
+ //# sourceMappingURL=chunk-ANZZEZP2.js.map
38
+ //# sourceMappingURL=chunk-ANZZEZP2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/status-bar/status-bar.tsx"],"names":[],"mappings":";;;AAuBO,SAAS,SAAA,CAAU;AAAA,EACxB,IAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,YAAA,EAAc,SAAA;AAAA,EACd,cAAA,EAAgB,UAAA;AAAA,EAChB;AACF,CAAA,EAAyD;AACvD,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,IAAA,EAAK,OAAA;AAAA,MACL,cAAY,SAAA,IAAa,YAAA;AAAA,MACzB,iBAAe,QAAA,IAAY,MAAA;AAAA,MAC3B,cAAA,EAAc,UAAA;AAAA,MACd,SAAA,EAAW,EAAA;AAAA,QACT,YAAA;AAAA,QACA,6DAAA;AAAA,QACA,QAAA,IAAY,gCAAA;AAAA,QACZ;AAAA,OACF;AAAA,MACC,QAAA,EAAA;AAAA,QAAA,IAAA,oBAAQ,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2BAAA,EAA6B,QAAA,EAAA,IAAA,EAAK,CAAA;AAAA,QACzD,KAAA,oBAAS,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qCAAqC,QAAA,EAAA,KAAA,EAAM;AAAA;AAAA;AAAA,GACtE;AAEJ","file":"chunk-ANZZEZP2.js","sourcesContent":["'use client'\n\nimport { cn } from '@/lib/utils'\nimport type { StatusBarProps } from './types'\n\n/**\n * StatusBar - A horizontal bar with left and right slots.\n *\n * Designed to sit above or below a text input (e.g., PromptArea) to\n * display contextual information. Place it as a sibling before or\n * after the input inside a shared wrapper.\n *\n * @example\n * ```tsx\n * <div className=\"rounded-lg border\">\n * <StatusBar\n * left={<span>prompt-area</span>}\n * right={<span>Default</span>}\n * />\n * <PromptArea value={segments} onChange={setSegments} ... />\n * </div>\n * ```\n */\nexport function StatusBar({\n left,\n right,\n className,\n disabled = false,\n 'aria-label': ariaLabel,\n 'data-test-id': dataTestId,\n ref,\n}: StatusBarProps & { ref?: React.Ref<HTMLDivElement> }) {\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-label={ariaLabel ?? 'Status bar'}\n aria-disabled={disabled || undefined}\n data-test-id={dataTestId}\n className={cn(\n 'status-bar',\n 'flex items-center justify-between gap-2 px-3 py-1.5 text-xs',\n disabled && 'pointer-events-none opacity-50',\n className,\n )}>\n {left && <div className=\"flex items-center gap-1.5\">{left}</div>}\n {right && <div className=\"ml-auto flex items-center gap-1.5\">{right}</div>}\n </div>\n )\n}\n"]}
@@ -0,0 +1,198 @@
1
+ 'use client';
2
+ import { BLUR_DELAY_MS, PromptArea } from './chunk-E7HUXORB.js';
3
+ import { cn } from './chunk-NF2LHZIE.js';
4
+ import { useRef, useState, useImperativeHandle, useCallback } from 'react';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ function Svg({ className, children }) {
8
+ return /* @__PURE__ */ jsx(
9
+ "svg",
10
+ {
11
+ xmlns: "http://www.w3.org/2000/svg",
12
+ width: "24",
13
+ height: "24",
14
+ viewBox: "0 0 24 24",
15
+ fill: "none",
16
+ stroke: "currentColor",
17
+ strokeWidth: "2",
18
+ strokeLinecap: "round",
19
+ strokeLinejoin: "round",
20
+ "aria-hidden": "true",
21
+ className,
22
+ children
23
+ }
24
+ );
25
+ }
26
+ var Plus = ({ className }) => /* @__PURE__ */ jsxs(Svg, { className, children: [
27
+ /* @__PURE__ */ jsx("path", { d: "M5 12h14" }),
28
+ /* @__PURE__ */ jsx("path", { d: "M12 5v14" })
29
+ ] });
30
+ var ArrowUp = ({ className }) => /* @__PURE__ */ jsxs(Svg, { className, children: [
31
+ /* @__PURE__ */ jsx("path", { d: "m5 12 7-7 7 7" }),
32
+ /* @__PURE__ */ jsx("path", { d: "M12 19V5" })
33
+ ] });
34
+ function CompactPromptArea({
35
+ value,
36
+ onChange,
37
+ triggers,
38
+ placeholder,
39
+ disabled = false,
40
+ markdown,
41
+ onSubmit,
42
+ onEscape,
43
+ onChipClick,
44
+ onChipAdd,
45
+ onChipDelete,
46
+ onPaste,
47
+ images,
48
+ onImagePaste,
49
+ onImageRemove,
50
+ files,
51
+ onFileRemove,
52
+ plusButtonIcon,
53
+ onPlusClick,
54
+ submitButtonIcon,
55
+ beforeSubmitSlot,
56
+ maxHeight = 320,
57
+ className,
58
+ "aria-label": ariaLabel,
59
+ "data-test-id": dataTestId,
60
+ ref
61
+ }) {
62
+ const promptRef = useRef(null);
63
+ const containerRef = useRef(null);
64
+ const [isFocused, setIsFocused] = useState(false);
65
+ useImperativeHandle(ref, () => promptRef.current, []);
66
+ const isEmpty = value.length === 0 || value.length === 1 && value[0].type === "text" && value[0].text === "";
67
+ const isExpanded = isFocused || !isEmpty;
68
+ const handleContainerFocus = useCallback(() => {
69
+ setIsFocused(true);
70
+ }, []);
71
+ const handleContainerBlur = useCallback(() => {
72
+ setTimeout(() => {
73
+ if (!containerRef.current?.contains(document.activeElement)) {
74
+ setIsFocused(false);
75
+ }
76
+ }, BLUR_DELAY_MS);
77
+ }, []);
78
+ const handleSubmit = useCallback(() => {
79
+ onSubmit?.(value);
80
+ }, [onSubmit, value]);
81
+ return /* @__PURE__ */ jsx(
82
+ "div",
83
+ {
84
+ ref: containerRef,
85
+ onFocus: handleContainerFocus,
86
+ onBlur: handleContainerBlur,
87
+ "aria-label": ariaLabel,
88
+ "data-test-id": dataTestId,
89
+ className: cn(
90
+ "compact-prompt-area",
91
+ "bg-background border transition-all duration-200 ease-out",
92
+ isExpanded ? "rounded-2xl" : "rounded-full",
93
+ className
94
+ ),
95
+ children: /* @__PURE__ */ jsxs("div", { className: cn("flex", isExpanded ? "flex-col" : "items-center p-1.5"), children: [
96
+ !isExpanded && /* @__PURE__ */ jsx(
97
+ "button",
98
+ {
99
+ type: "button",
100
+ onClick: onPlusClick,
101
+ disabled,
102
+ className: cn(
103
+ "flex shrink-0 items-center justify-center rounded-xl transition-colors",
104
+ "bg-muted text-muted-foreground size-9",
105
+ "hover:bg-accent hover:text-foreground",
106
+ "disabled:pointer-events-none disabled:opacity-50"
107
+ ),
108
+ "aria-label": "Add attachment",
109
+ children: plusButtonIcon ?? /* @__PURE__ */ jsx(Plus, { className: "size-4" })
110
+ }
111
+ ),
112
+ /* @__PURE__ */ jsx(
113
+ "div",
114
+ {
115
+ className: cn("min-w-0 flex-1", isExpanded ? "px-5 pt-4 pb-2" : "overflow-hidden px-3"),
116
+ onClick: () => promptRef.current?.focus(),
117
+ children: /* @__PURE__ */ jsx(
118
+ PromptArea,
119
+ {
120
+ ref: promptRef,
121
+ value,
122
+ onChange,
123
+ triggers,
124
+ placeholder,
125
+ disabled,
126
+ markdown,
127
+ onSubmit: handleSubmit,
128
+ onEscape,
129
+ onChipClick,
130
+ onChipAdd,
131
+ onChipDelete,
132
+ onPaste,
133
+ images,
134
+ onImagePaste,
135
+ onImageRemove,
136
+ files,
137
+ onFileRemove,
138
+ autoGrow: true,
139
+ minHeight: isExpanded ? 48 : 24,
140
+ maxHeight
141
+ }
142
+ )
143
+ }
144
+ ),
145
+ /* @__PURE__ */ jsxs(
146
+ "div",
147
+ {
148
+ className: cn(
149
+ "flex shrink-0 items-center",
150
+ isExpanded ? "justify-between px-3 pt-1 pb-3" : "gap-1.5"
151
+ ),
152
+ children: [
153
+ isExpanded && /* @__PURE__ */ jsx(
154
+ "button",
155
+ {
156
+ type: "button",
157
+ onClick: onPlusClick,
158
+ disabled,
159
+ className: cn(
160
+ "flex shrink-0 items-center justify-center rounded-xl transition-colors",
161
+ "bg-muted text-muted-foreground size-9",
162
+ "hover:bg-accent hover:text-foreground",
163
+ "disabled:pointer-events-none disabled:opacity-50"
164
+ ),
165
+ "aria-label": "Add attachment",
166
+ children: plusButtonIcon ?? /* @__PURE__ */ jsx(Plus, { className: "size-4" })
167
+ }
168
+ ),
169
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
170
+ beforeSubmitSlot,
171
+ /* @__PURE__ */ jsx(
172
+ "button",
173
+ {
174
+ type: "button",
175
+ onClick: handleSubmit,
176
+ disabled: disabled || isEmpty,
177
+ className: cn(
178
+ "flex shrink-0 items-center justify-center rounded-xl transition-colors",
179
+ "bg-primary text-primary-foreground size-9",
180
+ "hover:bg-primary/90",
181
+ "disabled:pointer-events-none disabled:opacity-50"
182
+ ),
183
+ "aria-label": "Send message",
184
+ children: submitButtonIcon ?? /* @__PURE__ */ jsx(ArrowUp, { className: "size-4" })
185
+ }
186
+ )
187
+ ] })
188
+ ]
189
+ }
190
+ )
191
+ ] })
192
+ }
193
+ );
194
+ }
195
+
196
+ export { CompactPromptArea };
197
+ //# sourceMappingURL=chunk-BPJO4DGM.js.map
198
+ //# sourceMappingURL=chunk-BPJO4DGM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/compact-prompt-area/compact-prompt-area.tsx"],"names":[],"mappings":"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","file":"chunk-BPJO4DGM.js","sourcesContent":["'use client'\n\nimport { useCallback, useImperativeHandle, useRef, useState } from 'react'\nimport { cn } from '@/lib/utils'\nimport { PromptArea } from '@/registry/new-york/blocks/prompt-area/prompt-area'\nimport { BLUR_DELAY_MS } from '@/registry/new-york/blocks/prompt-area/use-prompt-area-events'\nimport type { PromptAreaHandle } from '@/registry/new-york/blocks/prompt-area/types'\nimport type { CompactPromptAreaProps } from './types'\n\ntype IconProps = { className?: string }\n\n/** Shared SVG wrapper matching the lucide icon defaults (no dependency). */\nfunction Svg({ className, children }: IconProps & { children: React.ReactNode }) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className={className}>\n {children}\n </svg>\n )\n}\n\nconst Plus = ({ className }: IconProps) => (\n <Svg className={className}>\n <path d=\"M5 12h14\" />\n <path d=\"M12 5v14\" />\n </Svg>\n)\nconst ArrowUp = ({ className }: IconProps) => (\n <Svg className={className}>\n <path d=\"m5 12 7-7 7 7\" />\n <path d=\"M12 19V5\" />\n </Svg>\n)\n\n/**\n * CompactPromptArea – A pill-shaped prompt input that sits on a single row\n * and expands downward on focus.\n *\n * - Left: circular plus button\n * - Middle: PromptArea text input (expands down when focused)\n * - Right: optional slot + circular submit button\n *\n * Reuses PromptArea internally with autoGrow enabled.\n *\n * @example\n * ```tsx\n * <CompactPromptArea\n * value={segments}\n * onChange={setSegments}\n * placeholder=\"Ask anything...\"\n * onSubmit={handleSubmit}\n * onPlusClick={() => setMenuOpen(true)}\n * beforeSubmitSlot={<button aria-label=\"Voice\"><Mic className=\"size-4\" /></button>}\n * />\n * ```\n */\nexport function CompactPromptArea({\n value,\n onChange,\n triggers,\n placeholder,\n disabled = false,\n markdown,\n onSubmit,\n onEscape,\n onChipClick,\n onChipAdd,\n onChipDelete,\n onPaste,\n images,\n onImagePaste,\n onImageRemove,\n files,\n onFileRemove,\n plusButtonIcon,\n onPlusClick,\n submitButtonIcon,\n beforeSubmitSlot,\n maxHeight = 320,\n className,\n 'aria-label': ariaLabel,\n 'data-test-id': dataTestId,\n ref,\n}: CompactPromptAreaProps & { ref?: React.Ref<PromptAreaHandle> }) {\n const promptRef = useRef<PromptAreaHandle>(null)\n const containerRef = useRef<HTMLDivElement>(null)\n const [isFocused, setIsFocused] = useState(false)\n\n useImperativeHandle(ref, () => promptRef.current!, [])\n\n const isEmpty =\n value.length === 0 || (value.length === 1 && value[0].type === 'text' && value[0].text === '')\n\n const isExpanded = isFocused || !isEmpty\n\n const handleContainerFocus = useCallback(() => {\n setIsFocused(true)\n }, [])\n\n const handleContainerBlur = useCallback(() => {\n setTimeout(() => {\n if (!containerRef.current?.contains(document.activeElement)) {\n setIsFocused(false)\n }\n }, BLUR_DELAY_MS)\n }, [])\n\n const handleSubmit = useCallback(() => {\n onSubmit?.(value)\n }, [onSubmit, value])\n\n return (\n <div\n ref={containerRef}\n onFocus={handleContainerFocus}\n onBlur={handleContainerBlur}\n aria-label={ariaLabel}\n data-test-id={dataTestId}\n className={cn(\n 'compact-prompt-area',\n 'bg-background border transition-all duration-200 ease-out',\n isExpanded ? 'rounded-2xl' : 'rounded-full',\n className,\n )}>\n <div className={cn('flex', isExpanded ? 'flex-col' : 'items-center p-1.5')}>\n {/* Plus button – left side in collapsed mode */}\n {!isExpanded && (\n <button\n type=\"button\"\n onClick={onPlusClick}\n disabled={disabled}\n className={cn(\n 'flex shrink-0 items-center justify-center rounded-xl transition-colors',\n 'bg-muted text-muted-foreground size-9',\n 'hover:bg-accent hover:text-foreground',\n 'disabled:pointer-events-none disabled:opacity-50',\n )}\n aria-label=\"Add attachment\">\n {plusButtonIcon ?? <Plus className=\"size-4\" />}\n </button>\n )}\n\n {/* Prompt area region */}\n <div\n className={cn('min-w-0 flex-1', isExpanded ? 'px-5 pt-4 pb-2' : 'overflow-hidden px-3')}\n onClick={() => promptRef.current?.focus()}>\n <PromptArea\n ref={promptRef}\n value={value}\n onChange={onChange}\n triggers={triggers}\n placeholder={placeholder}\n disabled={disabled}\n markdown={markdown}\n onSubmit={handleSubmit}\n onEscape={onEscape}\n onChipClick={onChipClick}\n onChipAdd={onChipAdd}\n onChipDelete={onChipDelete}\n onPaste={onPaste}\n images={images}\n onImagePaste={onImagePaste}\n onImageRemove={onImageRemove}\n files={files}\n onFileRemove={onFileRemove}\n autoGrow\n minHeight={isExpanded ? 48 : 24}\n maxHeight={maxHeight}\n />\n </div>\n\n {/* Button bar */}\n <div\n className={cn(\n 'flex shrink-0 items-center',\n isExpanded ? 'justify-between px-3 pt-1 pb-3' : 'gap-1.5',\n )}>\n {/* Plus button – bottom-left in expanded mode */}\n {isExpanded && (\n <button\n type=\"button\"\n onClick={onPlusClick}\n disabled={disabled}\n className={cn(\n 'flex shrink-0 items-center justify-center rounded-xl transition-colors',\n 'bg-muted text-muted-foreground size-9',\n 'hover:bg-accent hover:text-foreground',\n 'disabled:pointer-events-none disabled:opacity-50',\n )}\n aria-label=\"Add attachment\">\n {plusButtonIcon ?? <Plus className=\"size-4\" />}\n </button>\n )}\n\n {/* Right side: slot + submit */}\n <div className=\"flex items-center gap-1.5\">\n {beforeSubmitSlot}\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={disabled || isEmpty}\n className={cn(\n 'flex shrink-0 items-center justify-center rounded-xl transition-colors',\n 'bg-primary text-primary-foreground size-9',\n 'hover:bg-primary/90',\n 'disabled:pointer-events-none disabled:opacity-50',\n )}\n aria-label=\"Send message\">\n {submitButtonIcon ?? <ArrowUp className=\"size-4\" />}\n </button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n"]}
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+ import { cn } from './chunk-NF2LHZIE.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ function ActionBar({
6
+ left,
7
+ right,
8
+ className,
9
+ disabled = false,
10
+ "aria-label": ariaLabel,
11
+ "data-test-id": dataTestId,
12
+ ref
13
+ }) {
14
+ return /* @__PURE__ */ jsxs(
15
+ "div",
16
+ {
17
+ ref,
18
+ role: "toolbar",
19
+ "aria-label": ariaLabel ?? "Action bar",
20
+ "aria-disabled": disabled || void 0,
21
+ "data-test-id": dataTestId,
22
+ className: cn(
23
+ "action-bar",
24
+ "flex items-center justify-between gap-2 pt-2",
25
+ disabled && "pointer-events-none opacity-50",
26
+ className
27
+ ),
28
+ children: [
29
+ left && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: left }),
30
+ right && /* @__PURE__ */ jsx("div", { className: "ml-auto flex items-center gap-1", children: right })
31
+ ]
32
+ }
33
+ );
34
+ }
35
+
36
+ export { ActionBar };
37
+ //# sourceMappingURL=chunk-BWVBDP7C.js.map
38
+ //# sourceMappingURL=chunk-BWVBDP7C.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/action-bar/action-bar.tsx"],"names":[],"mappings":";;;AAiCO,SAAS,SAAA,CAAU;AAAA,EACxB,IAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,YAAA,EAAc,SAAA;AAAA,EACd,cAAA,EAAgB,UAAA;AAAA,EAChB;AACF,CAAA,EAAyD;AACvD,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,IAAA,EAAK,SAAA;AAAA,MACL,cAAY,SAAA,IAAa,YAAA;AAAA,MACzB,iBAAe,QAAA,IAAY,MAAA;AAAA,MAC3B,cAAA,EAAc,UAAA;AAAA,MACd,SAAA,EAAW,EAAA;AAAA,QACT,YAAA;AAAA,QACA,8CAAA;AAAA,QACA,QAAA,IAAY,gCAAA;AAAA,QACZ;AAAA,OACF;AAAA,MACC,QAAA,EAAA;AAAA,QAAA,IAAA,oBAAQ,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAA,EAA2B,QAAA,EAAA,IAAA,EAAK,CAAA;AAAA,QACvD,KAAA,oBAAS,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mCAAmC,QAAA,EAAA,KAAA,EAAM;AAAA;AAAA;AAAA,GACpE;AAEJ","file":"chunk-BWVBDP7C.js","sourcesContent":["'use client'\n\nimport { cn } from '@/lib/utils'\nimport type { ActionBarProps } from './types'\n\n/**\n * ActionBar - A horizontal toolbar with left and right slots.\n *\n * Designed to sit below a text input (e.g., PromptArea) and stay\n * anchored via normal document flow. Place it as a sibling after\n * the input inside a shared wrapper.\n *\n * @example\n * ```tsx\n * <div className=\"rounded-lg border p-4\">\n * <PromptArea value={segments} onChange={setSegments} ... />\n * <ActionBar\n * left={\n * <>\n * <button><PlusCircle /></button>\n * <button><AtSign /></button>\n * </>\n * }\n * right={\n * <>\n * <button><Mic /></button>\n * <button onClick={handleSubmit}><ArrowUp /></button>\n * </>\n * }\n * />\n * </div>\n * ```\n */\nexport function ActionBar({\n left,\n right,\n className,\n disabled = false,\n 'aria-label': ariaLabel,\n 'data-test-id': dataTestId,\n ref,\n}: ActionBarProps & { ref?: React.Ref<HTMLDivElement> }) {\n return (\n <div\n ref={ref}\n role=\"toolbar\"\n aria-label={ariaLabel ?? 'Action bar'}\n aria-disabled={disabled || undefined}\n data-test-id={dataTestId}\n className={cn(\n 'action-bar',\n 'flex items-center justify-between gap-2 pt-2',\n disabled && 'pointer-events-none opacity-50',\n className,\n )}>\n {left && <div className=\"flex items-center gap-1\">{left}</div>}\n {right && <div className=\"ml-auto flex items-center gap-1\">{right}</div>}\n </div>\n )\n}\n"]}