@ctrl-ui/react 0.8.18 → 0.8.20

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 (106) hide show
  1. package/README.md +7 -0
  2. package/dist/action-bar.js +2 -2
  3. package/dist/audio-device-select.js +5 -2
  4. package/dist/blocks/coding-agent.js +22 -12
  5. package/dist/blocks/design-canvas-inspector.js +6 -3
  6. package/dist/blocks/design-canvas.js +20 -10
  7. package/dist/blocks/discussion-markdown-input.d.ts +14 -0
  8. package/dist/blocks/discussion-markdown-input.js +36 -0
  9. package/dist/blocks/discussion.d.ts +9 -3
  10. package/dist/blocks/discussion.js +82 -16
  11. package/dist/blocks/file-explorer.js +46 -24
  12. package/dist/blocks/settings.js +10 -6
  13. package/dist/chat-composer-editor/extensions/mention.d.ts +1 -1
  14. package/dist/chat-message.d.ts +13 -0
  15. package/dist/chat-message.js +198 -3
  16. package/dist/filter-bar/editor.js +5 -2
  17. package/dist/knob-contracts/flow-knobs.d.ts +4 -0
  18. package/dist/knob-contracts/flow-knobs.js +32 -0
  19. package/dist/knob-contracts/item-knobs.d.ts +1 -1
  20. package/dist/knob-contracts/item-knobs.js +4 -1
  21. package/dist/knob-contracts/label-knobs.d.ts +4 -0
  22. package/dist/knob-contracts/label-knobs.js +4 -0
  23. package/dist/knob-contracts/markdown-editor-knobs.d.ts +4 -0
  24. package/dist/knob-contracts/markdown-editor-knobs.js +17 -0
  25. package/dist/knob-contracts/morphing-panel-knobs.d.ts +1 -1
  26. package/dist/knob-contracts/morphing-panel-knobs.js +2 -1
  27. package/dist/knob-contracts/resize-handle-knobs.d.ts +4 -0
  28. package/dist/knob-contracts/resize-handle-knobs.js +12 -0
  29. package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
  30. package/dist/knob-contracts/sidebar-knobs.js +10 -0
  31. package/dist/knob-contracts/switch-knobs.d.ts +1 -1
  32. package/dist/knob-contracts/switch-knobs.js +1 -0
  33. package/dist/lib/canvas-grid.d.ts +4 -0
  34. package/dist/lib/canvas-grid.js +5 -0
  35. package/dist/markdown-editor/context.d.ts +22 -0
  36. package/dist/markdown-editor/context.js +10 -0
  37. package/dist/markdown-editor/drag-handle.d.ts +6 -0
  38. package/dist/markdown-editor/drag-handle.js +126 -0
  39. package/dist/markdown-editor/extensions.d.ts +10 -0
  40. package/dist/markdown-editor/extensions.js +139 -0
  41. package/dist/markdown-editor/suggestions.d.ts +12 -0
  42. package/dist/markdown-editor/suggestions.js +235 -0
  43. package/dist/markdown-editor/toolbar.d.ts +9 -0
  44. package/dist/markdown-editor/toolbar.js +339 -0
  45. package/dist/markdown-editor/uploads.d.ts +23 -0
  46. package/dist/markdown-editor/uploads.js +32 -0
  47. package/dist/markdown-editor/use-image-uploads.d.ts +12 -0
  48. package/dist/markdown-editor/use-image-uploads.js +120 -0
  49. package/dist/markdown-editor.d.ts +27 -0
  50. package/dist/markdown-editor.js +202 -0
  51. package/dist/styles/index.css +9 -4
  52. package/dist/styles/recipes/chat-message-edit.css +105 -0
  53. package/dist/styles/recipes/code.css +4 -0
  54. package/dist/styles/recipes/field.css +1 -1
  55. package/dist/styles/recipes/flow.css +304 -0
  56. package/dist/styles/recipes/infinite-canvas.css +1 -1
  57. package/dist/styles/recipes/item.css +39 -2
  58. package/dist/styles/recipes/label.css +19 -0
  59. package/dist/styles/recipes/markdown-editor.css +234 -0
  60. package/dist/styles/recipes/morphing-panel.css +53 -12
  61. package/dist/styles/recipes/page-layout.css +11 -1
  62. package/dist/styles/recipes/resize-handle.css +126 -0
  63. package/dist/styles/recipes/sidebar-mobile.css +171 -0
  64. package/dist/styles/recipes/switch.css +17 -5
  65. package/dist/styles/recipes/toolbar.css +8 -1
  66. package/dist/styles/theme.css +1 -0
  67. package/dist/ui/autocomplete.js +1 -1
  68. package/dist/ui/code.js +1 -0
  69. package/dist/ui/color-picker.js +1 -1
  70. package/dist/ui/combobox.d.ts +1 -1
  71. package/dist/ui/combobox.js +1 -1
  72. package/dist/ui/dockable-panel.js +1 -1
  73. package/dist/ui/flow.d.ts +51 -0
  74. package/dist/ui/flow.js +153 -0
  75. package/dist/ui/infinite-canvas.js +3 -3
  76. package/dist/ui/item.d.ts +5 -3
  77. package/dist/ui/item.js +2 -1
  78. package/dist/ui/label.d.ts +8 -0
  79. package/dist/ui/label.js +14 -0
  80. package/dist/ui/markdown.d.ts +2 -1
  81. package/dist/ui/markdown.js +3 -3
  82. package/dist/ui/morphing-panel.d.ts +21 -4
  83. package/dist/ui/morphing-panel.js +152 -36
  84. package/dist/ui/resize-handle.d.ts +11 -0
  85. package/dist/ui/resize-handle.js +26 -0
  86. package/dist/ui/sidebar-mobile.d.ts +17 -0
  87. package/dist/ui/sidebar-mobile.js +70 -0
  88. package/dist/ui/sidebar-provider.d.ts +1 -0
  89. package/dist/ui/sidebar-provider.js +3 -0
  90. package/dist/ui/sidebar.d.ts +3 -1
  91. package/dist/ui/sidebar.js +101 -49
  92. package/dist/ui/toolbar.d.ts +3 -1
  93. package/dist/ui/toolbar.js +2 -1
  94. package/dist/ui/typography.d.ts +47 -0
  95. package/dist/ui/typography.js +61 -0
  96. package/dist/ui/use-switch-drag.js +1 -1
  97. package/package.json +41 -1
  98. package/dist/environment-variables.d.ts +0 -125
  99. package/dist/environment-variables.js +0 -512
  100. package/dist/hooks/use-environment-variables.d.ts +0 -50
  101. package/dist/hooks/use-environment-variables.js +0 -188
  102. package/dist/knob-contracts/environment-variables-knobs.d.ts +0 -4
  103. package/dist/knob-contracts/environment-variables-knobs.js +0 -12
  104. package/dist/lib/env-file.d.ts +0 -27
  105. package/dist/lib/env-file.js +0 -218
  106. package/dist/styles/recipes/environment-variables.css +0 -170
package/README.md CHANGED
@@ -73,8 +73,15 @@ Install a peer only when you import the component that needs it.
73
73
  | `ui/calendar` | `react-day-picker` |
74
74
  | `ui/code`, `ui/code-diff` | `@tanstack/react-virtual`, `diff`, `shiki` |
75
75
  | `ui/command` | `cmdk` |
76
+ | `ui/flow` | `@xyflow/react` |
76
77
  | `ui/markdown` | `streamdown` |
77
78
  | `ui/phone-input` | `react-phone-number-input`, `libphonenumber-js`, `zod` |
78
79
  | `ui/resizable` | `react-resizable-panels` |
79
80
  | `chat-composer` | `prosemirror-*` |
80
81
  | `email/email`, `email/templates` | `react-email` |
82
+
83
+ `ui/flow` also needs the React Flow structural stylesheet, loaded under the recipes:
84
+
85
+ ```css
86
+ @import "@xyflow/react/dist/base.css" layer(base);
87
+ ```
@@ -92,14 +92,14 @@ function ActionBarEdit({ value, children = "Edit", disabled, onClick, ...props }
92
92
  if (event.defaultPrevented || !onEdit) return;
93
93
  try {
94
94
  const nextValue = await resolveCopyValue(editValue);
95
- if (!nextValue) return;
95
+ if (nextValue === void 0) return;
96
96
  onEdit(nextValue);
97
97
  } catch (error) {
98
98
  onEditError?.(error);
99
99
  }
100
100
  }
101
101
  return /* @__PURE__ */ jsx(ActionBarItem, {
102
- disabled: disabled ?? (!editValue || !onEdit),
102
+ disabled: disabled ?? (editValue === void 0 || !onEdit),
103
103
  onClick: handleClick,
104
104
  ...props,
105
105
  children
@@ -2,6 +2,7 @@
2
2
  import { cn } from "./lib/cn.js";
3
3
  import { Button } from "./ui/button.js";
4
4
  import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select.js";
5
+ import { Text } from "./ui/typography.js";
5
6
  import { createContext, use, useState } from "react";
6
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
8
  import { MicIcon } from "lucide-react";
@@ -118,8 +119,10 @@ function AudioDeviceSelectContent({ children, ...props }) {
118
119
  children: [/* @__PURE__ */ jsx("span", {
119
120
  className: "min-w-0 flex-1 truncate",
120
121
  children: deviceLabel(device, index)
121
- }), device.isDefault || device.deviceId === "default" ? /* @__PURE__ */ jsx("span", {
122
- className: "ms-3 text-caption text-muted-foreground",
122
+ }), device.isDefault || device.deviceId === "default" ? /* @__PURE__ */ jsx(Text, {
123
+ size: "caption",
124
+ tone: "muted",
125
+ className: "ms-3",
123
126
  children: "Default"
124
127
  }) : null]
125
128
  }, device.deviceId)),
@@ -2,6 +2,7 @@
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { Button } from "../ui/button.js";
4
4
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js";
5
+ import { Heading, Text } from "../ui/typography.js";
5
6
  import { ChatLayout, ChatThread } from "../chat-layout.js";
6
7
  import { SidebarProvider, useSidebar } from "../ui/sidebar-provider.js";
7
8
  import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar-menu.js";
@@ -10,7 +11,6 @@ import { jsx, jsxs } from "react/jsx-runtime";
10
11
  import { BotIcon, ChevronRightIcon, CircleIcon, FolderIcon, PlusIcon, SearchIcon } from "lucide-react";
11
12
  //#region src/blocks/coding-agent.tsx
12
13
  function CodingAgentBlock({ appName = "Agent", brand, sidebarTop, navigation = [], projects, activeTaskId, activeTaskTitle = "New task", onNavigationSelect, onTaskSelect, onNewTask, onSearch, headerActions, sidebarFooter, children, layout = "viewport", className, style, ...props }) {
13
- const Heading = layout === "contained" ? "h2" : "h1";
14
14
  const providerStyle = {
15
15
  "--sidebar-width": "17.5rem",
16
16
  ...style
@@ -46,7 +46,10 @@ function CodingAgentBlock({ appName = "Agent", brand, sidebarTop, navigation = [
46
46
  "aria-hidden": "true"
47
47
  }),
48
48
  /* @__PURE__ */ jsx(Heading, {
49
- className: "min-w-0 flex-1 truncate text-label font-medium",
49
+ level: layout === "contained" ? 2 : 1,
50
+ size: "label",
51
+ weight: "medium",
52
+ className: "min-w-0 flex-1 truncate",
50
53
  title: activeTaskTitle,
51
54
  children: activeTaskTitle
52
55
  }),
@@ -88,8 +91,10 @@ function CodingAgentSidebar({ appName, brand, sidebarTop, navigation, projects,
88
91
  className: "size-3.5",
89
92
  "aria-hidden": "true"
90
93
  })
91
- }), /* @__PURE__ */ jsx("span", {
92
- className: "truncate text-label font-semibold",
94
+ }), /* @__PURE__ */ jsx(Text, {
95
+ size: "label",
96
+ weight: "semibold",
97
+ className: "truncate",
93
98
  title: appName,
94
99
  children: appName
95
100
  })]
@@ -214,12 +219,14 @@ function CodingAgentEmptyState({ title = "What should we build?", description, i
214
219
  "aria-hidden": "true"
215
220
  })
216
221
  }),
217
- /* @__PURE__ */ jsx("h2", {
218
- className: "text-balance text-heading-3 sm:text-heading-2",
222
+ /* @__PURE__ */ jsx(Heading, {
223
+ level: 2,
219
224
  children: title
220
225
  }),
221
- description ? /* @__PURE__ */ jsx("p", {
222
- className: "mt-2 max-w-xl text-pretty text-body text-muted-foreground",
226
+ description ? /* @__PURE__ */ jsx(Text, {
227
+ as: "p",
228
+ tone: "muted",
229
+ className: "mt-2 max-w-xl text-pretty",
223
230
  children: description
224
231
  }) : null,
225
232
  suggestions.length > 0 ? /* @__PURE__ */ jsx("div", {
@@ -236,12 +243,15 @@ function CodingAgentEmptyState({ title = "What should we build?", description, i
236
243
  className: "text-primary-text",
237
244
  children: suggestion.icon
238
245
  }),
239
- /* @__PURE__ */ jsx("span", {
240
- className: "text-body font-medium text-foreground",
246
+ /* @__PURE__ */ jsx(Text, {
247
+ weight: "medium",
248
+ tone: "foreground",
241
249
  children: suggestion.title
242
250
  }),
243
- suggestion.description ? /* @__PURE__ */ jsx("span", {
244
- className: "text-caption leading-5 text-muted-foreground",
251
+ suggestion.description ? /* @__PURE__ */ jsx(Text, {
252
+ size: "caption",
253
+ tone: "muted",
254
+ className: "leading-5",
245
255
  children: suggestion.description
246
256
  }) : null
247
257
  ]
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { Button } from "../ui/button.js";
3
- import { InputGroup, InputGroupAddon } from "../ui/input-group.js";
3
+ import { Heading } from "../ui/typography.js";
4
4
  import { resizeDesignCanvasLayer } from "./design-canvas-data.js";
5
+ import { InputGroup, InputGroupAddon } from "../ui/input-group.js";
5
6
  import { NumberField, NumberFieldGroup, NumberFieldInput, NumberFieldScrubArea } from "../ui/number-field.js";
6
7
  import { ColorPicker, ColorPickerArea, ColorPickerContent, ColorPickerEyeDropper, ColorPickerHue, ColorPickerInput, ColorPickerTrigger } from "../ui/color-picker.js";
7
8
  import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "../ui/empty.js";
@@ -136,8 +137,10 @@ function InspectorSection({ title, action, children }) {
136
137
  className: "flex flex-col gap-2 border-b px-3 py-3",
137
138
  children: [/* @__PURE__ */ jsxs("header", {
138
139
  className: "flex h-6 items-center justify-between",
139
- children: [/* @__PURE__ */ jsx("h3", {
140
- className: "text-caption font-medium",
140
+ children: [/* @__PURE__ */ jsx(Heading, {
141
+ level: 3,
142
+ size: "caption",
143
+ weight: "medium",
141
144
  children: title
142
145
  }), action]
143
146
  }), /* @__PURE__ */ jsx("div", {
@@ -2,10 +2,11 @@
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { Button } from "../ui/button.js";
4
4
  import { ScrollArea } from "../ui/scroll-area.js";
5
+ import { Heading, Text } from "../ui/typography.js";
5
6
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip.js";
7
+ import { Drawer, DrawerContent, DrawerTitle } from "../ui/drawer.js";
6
8
  import { createDesignCanvasLayer, designCanvasFillColor, drawnDesignCanvasBounds } from "./design-canvas-data.js";
7
9
  import { DesignCanvasInspector } from "./design-canvas-inspector.js";
8
- import { Drawer, DrawerContent, DrawerTitle } from "../ui/drawer.js";
9
10
  import { InfiniteCanvas, InfiniteCanvasContent, InfiniteCanvasControls, InfiniteCanvasItem } from "../ui/infinite-canvas.js";
10
11
  import { ResizableFloatingPanel } from "../ui/resizable.js";
11
12
  import { Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator } from "../ui/toolbar.js";
@@ -344,7 +345,7 @@ function DesignCanvasBlock({ layers, defaultLayers = [], onLayersChange, selecte
344
345
  });
345
346
  }
346
347
  function DesignCanvasPanel({ label, title, inDrawer = false, children }) {
347
- const titleClassName = "flex h-11 shrink-0 items-center border-b px-3 text-caption font-medium";
348
+ const titleClassName = "flex h-11 shrink-0 items-center border-b px-3";
348
349
  const titleText = /* @__PURE__ */ jsx("span", {
349
350
  className: "truncate",
350
351
  title,
@@ -354,9 +355,12 @@ function DesignCanvasPanel({ label, title, inDrawer = false, children }) {
354
355
  "aria-label": label,
355
356
  className: "flex min-h-0 flex-1 flex-col",
356
357
  children: [inDrawer ? /* @__PURE__ */ jsx(DrawerTitle, {
357
- className: titleClassName,
358
+ className: `${titleClassName} text-caption font-medium`,
358
359
  children: titleText
359
- }) : /* @__PURE__ */ jsx("h2", {
360
+ }) : /* @__PURE__ */ jsx(Heading, {
361
+ level: 2,
362
+ size: "caption",
363
+ weight: "medium",
360
364
  className: titleClassName,
361
365
  children: titleText
362
366
  }), /* @__PURE__ */ jsx(ScrollArea, {
@@ -410,12 +414,17 @@ function DesignCanvasLayerNode({ layer, selected, onSelect, onKeyDown, onLayerCh
410
414
  y: Math.round(position.y)
411
415
  })),
412
416
  children: [
413
- layer.kind === "frame" ? /* @__PURE__ */ jsx("span", {
414
- className: "absolute start-0 bottom-full mb-1 whitespace-nowrap text-caption text-muted-foreground",
417
+ layer.kind === "frame" ? /* @__PURE__ */ jsx(Text, {
418
+ size: "caption",
419
+ tone: "muted",
420
+ className: "absolute start-0 bottom-full mb-1 whitespace-nowrap",
415
421
  children: layer.name
416
422
  }) : null,
417
- layer.kind === "text" ? /* @__PURE__ */ jsx("p", {
418
- className: "size-full overflow-hidden text-2xl leading-8 whitespace-nowrap",
423
+ layer.kind === "text" ? /* @__PURE__ */ jsx(Text, {
424
+ as: "p",
425
+ size: "heading-2",
426
+ weight: "normal",
427
+ className: "size-full overflow-hidden whitespace-nowrap",
419
428
  style: { color: fillColor },
420
429
  children: layer.text
421
430
  }) : /* @__PURE__ */ jsx("div", {
@@ -425,8 +434,9 @@ function DesignCanvasLayerNode({ layer, selected, onSelect, onKeyDown, onLayerCh
425
434
  borderRadius: layer.kind === "ellipse" ? "50%" : layer.cornerRadius
426
435
  }
427
436
  }),
428
- selected ? /* @__PURE__ */ jsxs("span", {
429
- className: "absolute inset-x-0 top-full mx-auto mt-1.5 w-max rounded-sm bg-primary px-1 text-micro whitespace-nowrap text-primary-foreground tabular-nums",
437
+ selected ? /* @__PURE__ */ jsxs(Text, {
438
+ size: "micro",
439
+ className: "absolute inset-x-0 top-full mx-auto mt-1.5 w-max rounded-sm bg-primary px-1 whitespace-nowrap text-primary-foreground tabular-nums",
430
440
  children: [
431
441
  layer.width,
432
442
  " × ",
@@ -0,0 +1,14 @@
1
+ import { MarkdownImageUploader } from "../markdown-editor/uploads.js";
2
+ import { MarkdownEditorStatus } from "../markdown-editor.js";
3
+ import { MarkdownEditorMention } from "../markdown-editor/suggestions.js";
4
+ //#region src/blocks/discussion-markdown-input.d.ts
5
+ export default function DiscussionMarkdownInput({ label, placeholder, autoFocus, onUploadImage, mentions, onStatusChange, blocked }: {
6
+ label: string;
7
+ placeholder?: string;
8
+ autoFocus?: boolean;
9
+ onUploadImage?: MarkdownImageUploader;
10
+ mentions?: readonly MarkdownEditorMention[];
11
+ onStatusChange: (status: MarkdownEditorStatus) => void;
12
+ blocked: boolean;
13
+ }): import("react").JSX.Element;
14
+ //#endregion
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { useChatComposerContext } from "../chat-composer.js";
3
+ import { MarkdownEditor, MarkdownEditorContent } from "../markdown-editor.js";
4
+ import { MarkdownEditorSuggestions } from "../markdown-editor/suggestions.js";
5
+ import { MarkdownEditorToolbar, MarkdownEditorUploads } from "../markdown-editor/toolbar.js";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/blocks/discussion-markdown-input.tsx
8
+ function DiscussionMarkdownInput({ label, placeholder, autoFocus, onUploadImage, mentions, onStatusChange, blocked }) {
9
+ const composer = useChatComposerContext();
10
+ return /* @__PURE__ */ jsxs(MarkdownEditor, {
11
+ value: composer.value,
12
+ onValueChange: composer.setValue,
13
+ disabled: composer.isDisabled || composer.isLocked,
14
+ onUploadImage,
15
+ onStatusChange,
16
+ onKeyDownCapture: (event) => {
17
+ if (!(event.target instanceof HTMLElement) || !event.target.isContentEditable && event.target.tagName !== "TEXTAREA") return;
18
+ if (event.key !== "Enter" || !(event.metaKey || event.ctrlKey) || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229) return;
19
+ event.preventDefault();
20
+ event.stopPropagation();
21
+ if (!blocked) composer.submit();
22
+ },
23
+ children: [
24
+ /* @__PURE__ */ jsx(MarkdownEditorToolbar, {}),
25
+ /* @__PURE__ */ jsx(MarkdownEditorContent, {
26
+ label,
27
+ placeholder,
28
+ autoFocus
29
+ }),
30
+ /* @__PURE__ */ jsx(MarkdownEditorSuggestions, { mentions }),
31
+ /* @__PURE__ */ jsx(MarkdownEditorUploads, {})
32
+ ]
33
+ });
34
+ }
35
+ //#endregion
36
+ export { DiscussionMarkdownInput as default };
@@ -1,3 +1,5 @@
1
+ import { MarkdownImageUploader } from "../markdown-editor/uploads.js";
2
+ import { MarkdownEditorMention } from "../markdown-editor/suggestions.js";
1
3
  import { ChatComposer } from "../chat-composer.js";
2
4
  import { ChatMessage } from "../chat-message.js";
3
5
  import { ComponentProps, ReactNode } from "react";
@@ -9,20 +11,24 @@ export type DiscussionComposerProps = Omit<ComponentProps<typeof ChatComposer>,
9
11
  secondaryAction?: ReactNode;
10
12
  hint?: ReactNode;
11
13
  autoFocus?: boolean;
14
+ format?: "plain" | "markdown";
15
+ onUploadImage?: MarkdownImageUploader;
16
+ mentions?: readonly MarkdownEditorMention[];
12
17
  };
13
- export declare function DiscussionComposer({ label, placeholder, submitLabel, secondaryAction, hint, autoFocus, ...props }: DiscussionComposerProps): import("react").JSX.Element;
18
+ export declare function DiscussionComposer({ label, placeholder, submitLabel, secondaryAction, hint, autoFocus, format, onUploadImage, mentions, onSubmit, ...props }: DiscussionComposerProps): import("react").JSX.Element;
14
19
  export type DiscussionCommentProps = Omit<ComponentProps<typeof ChatMessage>, "from" | "layout" | "authorLabel" | "children"> & {
15
20
  author: string;
16
21
  avatar?: ReactNode;
17
22
  sentAt: string;
18
23
  timeLabel: ReactNode;
19
24
  edited?: boolean;
20
- children: ReactNode;
25
+ children?: ReactNode;
26
+ markdown?: string;
21
27
  actions?: ReactNode;
22
28
  onReply?: () => void;
23
29
  replyLabel?: ReactNode;
24
30
  replies?: ReactNode;
25
31
  replyComposer?: ReactNode;
26
32
  };
27
- export declare function DiscussionComment({ author, avatar, sentAt, timeLabel, edited, children, actions, onReply, replyLabel, replies, replyComposer, ...props }: DiscussionCommentProps): import("react").JSX.Element;
33
+ export declare function DiscussionComment({ author, avatar, sentAt, timeLabel, edited, children, markdown, actions, onReply, replyLabel, replies, replyComposer, ...props }: DiscussionCommentProps): import("react").JSX.Element;
28
34
  //#endregion
@@ -1,34 +1,97 @@
1
1
  "use client";
2
2
  import { Button } from "../ui/button.js";
3
+ import { Text } from "../ui/typography.js";
3
4
  import { ChatComposer, ChatComposerShell, ChatComposerSubmit, ChatComposerTextarea, ChatComposerToolbar } from "../chat-composer.js";
4
5
  import { ChatMessageReplies } from "../chat-message/social.js";
5
6
  import { ChatMessage, ChatMessageActions, ChatMessageAuthor, ChatMessageAvatar, ChatMessageBody, ChatMessageContent, ChatMessageFooter, ChatMessageHeader, ChatMessageRow, ChatMessageTime } from "../chat-message.js";
7
+ import { Markdown } from "../ui/markdown.js";
6
8
  import { Kbd, KbdGroup } from "../ui/kbd.js";
7
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ import { Suspense, lazy, useState } from "react";
10
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
11
  import { CornerDownRightIcon } from "lucide-react";
9
12
  //#region src/blocks/discussion.tsx
10
- function DiscussionComposer({ label, placeholder, submitLabel = "Post", secondaryAction, hint = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(KbdGroup, { children: [/* @__PURE__ */ jsx(Kbd, { children: "⌘" }), /* @__PURE__ */ jsx(Kbd, { children: "Enter" })] }), /* @__PURE__ */ jsx("span", { children: "to post" })] }), autoFocus, ...props }) {
13
+ const DiscussionMarkdownInput = lazy(() => import("./discussion-markdown-input.js"));
14
+ function DiscussionComposer({ label, placeholder, submitLabel = "Post", secondaryAction, hint = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(KbdGroup, { children: [/* @__PURE__ */ jsx(Kbd, { children: "⌘" }), /* @__PURE__ */ jsx(Kbd, { children: "Enter" })] }), /* @__PURE__ */ jsx("span", { children: "to post" })] }), autoFocus, format = "plain", onUploadImage, mentions, onSubmit, ...props }) {
15
+ const [draft, setDraft] = useState(props.defaultValue ?? "");
16
+ const value = props.value ?? draft;
17
+ const [status, setStatus] = useState({
18
+ isEmpty: false,
19
+ hasPendingUploads: false
20
+ });
21
+ const [submitting, setSubmitting] = useState(false);
22
+ const [error, setError] = useState(null);
23
+ const blocked = format === "markdown" && (status.isEmpty || status.hasPendingUploads);
11
24
  return /* @__PURE__ */ jsx(ChatComposer, {
12
25
  submitKey: "mod-enter",
13
26
  ...props,
14
- children: /* @__PURE__ */ jsxs(ChatComposerShell, { children: [/* @__PURE__ */ jsx(ChatComposerTextarea, {
15
- "aria-label": label,
16
- placeholder,
17
- autoFocus,
18
- "aria-keyshortcuts": "Meta+Enter Control+Enter"
19
- }), /* @__PURE__ */ jsxs(ChatComposerToolbar, { children: [/* @__PURE__ */ jsx("span", {
20
- className: "flex min-w-0 items-center gap-1.5 text-caption text-muted-foreground",
21
- children: hint
22
- }), /* @__PURE__ */ jsxs("span", {
23
- className: "flex items-center gap-2",
24
- children: [secondaryAction, /* @__PURE__ */ jsx(ChatComposerSubmit, { children: submitLabel })]
25
- })] })] })
27
+ value,
28
+ onValueChange: (next) => {
29
+ setDraft(next);
30
+ props.onValueChange?.(next);
31
+ },
32
+ state: submitting ? "submitting" : props.state,
33
+ onSubmit: async (payload) => {
34
+ if (blocked || submitting) return;
35
+ setSubmitting(true);
36
+ setError(null);
37
+ try {
38
+ await onSubmit?.({
39
+ ...payload,
40
+ value: format === "markdown" ? value : payload.value
41
+ });
42
+ } catch (cause) {
43
+ setError(cause instanceof Error ? cause.message : "Couldn't post your comment. Please try again.");
44
+ } finally {
45
+ setSubmitting(false);
46
+ }
47
+ },
48
+ children: /* @__PURE__ */ jsxs(ChatComposerShell, { children: [
49
+ format === "markdown" ? /* @__PURE__ */ jsx(Suspense, {
50
+ fallback: /* @__PURE__ */ jsx(ChatComposerTextarea, {
51
+ "aria-label": label,
52
+ placeholder,
53
+ disabled: true
54
+ }),
55
+ children: /* @__PURE__ */ jsx(DiscussionMarkdownInput, {
56
+ label,
57
+ placeholder,
58
+ autoFocus,
59
+ onUploadImage,
60
+ mentions,
61
+ onStatusChange: setStatus,
62
+ blocked
63
+ })
64
+ }) : /* @__PURE__ */ jsx(ChatComposerTextarea, {
65
+ "aria-label": label,
66
+ placeholder,
67
+ autoFocus,
68
+ "aria-keyshortcuts": "Meta+Enter Control+Enter"
69
+ }),
70
+ error ? /* @__PURE__ */ jsx(Text, {
71
+ as: "p",
72
+ role: "alert",
73
+ className: "px-3",
74
+ children: error
75
+ }) : null,
76
+ /* @__PURE__ */ jsxs(ChatComposerToolbar, { children: [/* @__PURE__ */ jsx(Text, {
77
+ size: "caption",
78
+ tone: "muted",
79
+ className: "flex min-w-0 items-center gap-1.5",
80
+ children: hint
81
+ }), /* @__PURE__ */ jsxs("span", {
82
+ className: "flex items-center gap-2",
83
+ children: [secondaryAction, /* @__PURE__ */ jsx(ChatComposerSubmit, {
84
+ disabled: blocked || submitting,
85
+ children: submitLabel
86
+ })]
87
+ })] })
88
+ ] })
26
89
  });
27
90
  }
28
91
  function initials(name) {
29
92
  return name.split(/\s+/).slice(0, 2).map((word) => word.charAt(0).toUpperCase()).join("");
30
93
  }
31
- function DiscussionComment({ author, avatar, sentAt, timeLabel, edited = false, children, actions, onReply, replyLabel = "Reply", replies, replyComposer, ...props }) {
94
+ function DiscussionComment({ author, avatar, sentAt, timeLabel, edited = false, children, markdown, actions, onReply, replyLabel = "Reply", replies, replyComposer, ...props }) {
32
95
  const hasReplies = Boolean(replies) || Boolean(replyComposer);
33
96
  return /* @__PURE__ */ jsx(ChatMessage, {
34
97
  from: "participant",
@@ -49,7 +112,10 @@ function DiscussionComment({ author, avatar, sentAt, timeLabel, edited = false,
49
112
  }),
50
113
  edited ? /* @__PURE__ */ jsx("span", { children: "(edited)" }) : null
51
114
  ] }),
52
- /* @__PURE__ */ jsx(ChatMessageContent, { children }),
115
+ /* @__PURE__ */ jsx(ChatMessageContent, { children: markdown !== void 0 ? /* @__PURE__ */ jsx(Markdown, {
116
+ content: markdown,
117
+ mode: "static"
118
+ }) : children }),
53
119
  onReply ? /* @__PURE__ */ jsx(ChatMessageFooter, { children: /* @__PURE__ */ jsxs(Button, {
54
120
  variant: "ghost",
55
121
  size: "xs",
@@ -3,13 +3,14 @@ import { cn } from "../lib/cn.js";
3
3
  import { Button } from "../ui/button.js";
4
4
  import { LiveStatus } from "../ui/live-status.js";
5
5
  import { ScrollArea } from "../ui/scroll-area.js";
6
- import { InputGroup, InputGroupAddon } from "../ui/input-group.js";
7
- import { Input } from "../ui/input.js";
6
+ import { Heading, Text } from "../ui/typography.js";
7
+ import { Drawer, DrawerBody, DrawerContent, DrawerTitle, DrawerTrigger } from "../ui/drawer.js";
8
8
  import { SidebarProvider, useSidebar } from "../ui/sidebar-provider.js";
9
9
  import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar-menu.js";
10
10
  import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarInset, SidebarRail, SidebarTrigger } from "../ui/sidebar.js";
11
+ import { InputGroup, InputGroupAddon } from "../ui/input-group.js";
12
+ import { Input } from "../ui/input.js";
11
13
  import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty.js";
12
- import { Drawer, DrawerBody, DrawerContent, DrawerTitle, DrawerTrigger } from "../ui/drawer.js";
13
14
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "../ui/resizable.js";
14
15
  import { groupFileExplorerItems, orderFileExplorerEntries, resolveFileExplorer, searchFileExplorer } from "./file-explorer-data.js";
15
16
  import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "../ui/breadcrumb.js";
@@ -81,7 +82,7 @@ function FileExplorerBlock({ locations, activeLocationId, defaultActiveLocationI
81
82
  children: [
82
83
  /* @__PURE__ */ jsx(FileExplorerHeader, {
83
84
  location: activeLocation,
84
- headingAs: contained ? "h2" : "h1",
85
+ headingLevel: contained ? 2 : 1,
85
86
  breadcrumbs: resolution.breadcrumbs,
86
87
  query,
87
88
  searchPlaceholder,
@@ -110,7 +111,7 @@ function FileExplorerBlock({ locations, activeLocationId, defaultActiveLocationI
110
111
  columns: resolution.columns,
111
112
  selectedEntry: resolution.selectedEntry,
112
113
  selectedPath: resolution.validPath,
113
- headingAs: contained ? "h3" : "h2",
114
+ headingLevel: contained ? 3 : 2,
114
115
  backLabel,
115
116
  detailsLabel,
116
117
  onNavigate: changePath,
@@ -164,7 +165,7 @@ function FileExplorerSidebar({ locations, activeLocationId, sidebarTop, sidebarF
164
165
  ]
165
166
  });
166
167
  }
167
- function FileExplorerHeader({ location, headingAs: Heading, breadcrumbs, query, searchPlaceholder, searchLabel, clearSearchLabel, backLabel, moreActionsLabel, canGoBack, headerActions, headerMenuItems, onBack, onBreadcrumbSelect, onQueryChange }) {
168
+ function FileExplorerHeader({ location, headingLevel, breadcrumbs, query, searchPlaceholder, searchLabel, clearSearchLabel, backLabel, moreActionsLabel, canGoBack, headerActions, headerMenuItems, onBack, onBreadcrumbSelect, onQueryChange }) {
168
169
  return /* @__PURE__ */ jsxs("header", {
169
170
  className: "shrink-0 border-b border-border/70 bg-card/35",
170
171
  children: [/* @__PURE__ */ jsxs("div", {
@@ -184,7 +185,10 @@ function FileExplorerHeader({ location, headingAs: Heading, breadcrumbs, query,
184
185
  })
185
186
  }),
186
187
  /* @__PURE__ */ jsx(Heading, {
187
- className: "min-w-0 flex-1 truncate text-label font-semibold",
188
+ level: headingLevel,
189
+ size: "label",
190
+ weight: "semibold",
191
+ className: "min-w-0 flex-1 truncate",
188
192
  title: location.label,
189
193
  children: location.label
190
194
  }),
@@ -245,7 +249,7 @@ function FileExplorerHeader({ location, headingAs: Heading, breadcrumbs, query,
245
249
  })]
246
250
  });
247
251
  }
248
- function FileExplorerColumns({ columns, selectedEntry, selectedPath, headingAs, backLabel, detailsLabel, onNavigate, onOpen }) {
252
+ function FileExplorerColumns({ columns, selectedEntry, selectedPath, headingLevel, backLabel, detailsLabel, onNavigate, onOpen }) {
249
253
  const rootRef = useRef(null);
250
254
  const optionElements = useRef(/* @__PURE__ */ new Map());
251
255
  const pendingFocus = useRef(null);
@@ -381,8 +385,10 @@ function FileExplorerColumns({ columns, selectedEntry, selectedPath, headingAs,
381
385
  className: "truncate",
382
386
  children: parentColumn.label
383
387
  })]
384
- }) : /* @__PURE__ */ jsx("span", {
385
- className: "min-w-0 truncate px-1 text-label font-medium",
388
+ }) : /* @__PURE__ */ jsx(Text, {
389
+ size: "label",
390
+ weight: "medium",
391
+ className: "min-w-0 truncate px-1",
386
392
  children: activeColumn?.label
387
393
  }), /* @__PURE__ */ jsxs(Drawer, { children: [/* @__PURE__ */ jsx(DrawerTrigger, {
388
394
  render: /* @__PURE__ */ jsx(Button, {
@@ -395,7 +401,7 @@ function FileExplorerColumns({ columns, selectedEntry, selectedPath, headingAs,
395
401
  }), /* @__PURE__ */ jsx(DrawerContent, { children: selectedEntry ? /* @__PURE__ */ jsx(DrawerBody, { children: /* @__PURE__ */ jsx(FileExplorerPreviewContent, {
396
402
  entry: selectedEntry,
397
403
  title: /* @__PURE__ */ jsx(DrawerTitle, {
398
- className: "mt-4 max-w-full truncate text-heading-4 font-semibold",
404
+ className: "mt-4 max-w-full truncate text-heading-4",
399
405
  children: selectedEntry.name
400
406
  })
401
407
  }) }) : null })] })]
@@ -418,7 +424,7 @@ function FileExplorerColumns({ columns, selectedEntry, selectedPath, headingAs,
418
424
  minSize: "20%",
419
425
  children: /* @__PURE__ */ jsx(FileExplorerPreview, {
420
426
  entry: selectedEntry,
421
- headingAs
427
+ headingLevel
422
428
  })
423
429
  })]
424
430
  }) : null
@@ -502,10 +508,14 @@ function FileExplorerColumnView({ column, optionRef, onSelect, onOpen, onOptionK
502
508
  role: "group",
503
509
  "aria-labelledby": labelId,
504
510
  className: "grid min-w-0 gap-0.5",
505
- children: [/* @__PURE__ */ jsx("div", {
511
+ children: [/* @__PURE__ */ jsx(Text, {
506
512
  role: "presentation",
507
513
  id: labelId,
508
- className: "mb-1 px-2 text-caption font-semibold text-muted-foreground",
514
+ as: "div",
515
+ size: "caption",
516
+ weight: "semibold",
517
+ tone: "muted",
518
+ className: "mb-1 px-2",
509
519
  children: group.label
510
520
  }), options]
511
521
  }, group.label) : /* @__PURE__ */ jsx("div", {
@@ -517,7 +527,7 @@ function FileExplorerColumnView({ column, optionRef, onSelect, onOpen, onOptionK
517
527
  })
518
528
  });
519
529
  }
520
- function FileExplorerPreview({ entry, headingAs: Heading }) {
530
+ function FileExplorerPreview({ entry, headingLevel }) {
521
531
  if (!entry) return /* @__PURE__ */ jsx(Empty, {
522
532
  className: "h-full rounded-none bg-background/45",
523
533
  children: /* @__PURE__ */ jsxs(EmptyHeader, { children: [
@@ -533,7 +543,9 @@ function FileExplorerPreview({ entry, headingAs: Heading }) {
533
543
  entry,
534
544
  className: "px-6 py-10",
535
545
  title: /* @__PURE__ */ jsx(Heading, {
536
- className: "mt-4 max-w-full truncate text-heading-4 font-semibold",
546
+ level: headingLevel,
547
+ size: "heading-4",
548
+ className: "mt-4 max-w-full truncate",
537
549
  title: entry.name,
538
550
  children: entry.name
539
551
  })
@@ -549,8 +561,11 @@ function FileExplorerPreviewContent({ entry, title, className }) {
549
561
  children: /* @__PURE__ */ jsx(FileExplorerEntryIcon, { entry })
550
562
  }),
551
563
  title,
552
- entry.description ? /* @__PURE__ */ jsx("div", {
553
- className: "mt-1 text-caption text-muted-foreground",
564
+ entry.description ? /* @__PURE__ */ jsx(Text, {
565
+ as: "div",
566
+ size: "caption",
567
+ tone: "muted",
568
+ className: "mt-1",
554
569
  children: entry.description
555
570
  }) : null,
556
571
  entry.details && entry.details.length > 0 ? /* @__PURE__ */ jsx("dl", {
@@ -613,8 +628,11 @@ function FileExplorerSearchResults({ label, status, clearSearchLabel, results, o
613
628
  lockAxis: "x",
614
629
  children: /* @__PURE__ */ jsxs("div", {
615
630
  className: "mx-auto grid w-full max-w-3xl gap-1 p-4",
616
- children: [/* @__PURE__ */ jsx("div", {
617
- className: "mb-2 px-2 text-caption text-muted-foreground tabular-nums",
631
+ children: [/* @__PURE__ */ jsx(Text, {
632
+ as: "div",
633
+ size: "caption",
634
+ tone: "muted",
635
+ className: "mb-2 px-2 tabular-nums",
618
636
  children: status
619
637
  }), /* @__PURE__ */ jsx("div", {
620
638
  role: "listbox",
@@ -639,11 +657,15 @@ function FileExplorerSearchResults({ label, status, clearSearchLabel, results, o
639
657
  children: /* @__PURE__ */ jsx(FileExplorerEntryIcon, { entry: result.entry })
640
658
  }), /* @__PURE__ */ jsxs("span", {
641
659
  className: "min-w-0 flex-1",
642
- children: [/* @__PURE__ */ jsx("span", {
643
- className: "block truncate text-label text-foreground",
660
+ children: [/* @__PURE__ */ jsx(Text, {
661
+ size: "label",
662
+ tone: "foreground",
663
+ className: "block truncate",
644
664
  children: result.entry.name
645
- }), /* @__PURE__ */ jsx("span", {
646
- className: "block truncate text-caption text-muted-foreground",
665
+ }), /* @__PURE__ */ jsx(Text, {
666
+ size: "caption",
667
+ tone: "muted",
668
+ className: "block truncate",
647
669
  children: result.parents.join(" / ") || "Top level"
648
670
  })]
649
671
  })]