@tulipfarm/sprout 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +183 -2
- package/dist/components/accordion.d.ts +28 -0
- package/dist/components/accordion.js +26 -0
- package/dist/components/alert-dialog.d.ts +60 -0
- package/dist/components/alert-dialog.js +69 -0
- package/dist/components/alert.d.ts +30 -0
- package/dist/components/alert.js +35 -0
- package/dist/components/aspect-ratio.d.ts +7 -0
- package/dist/components/aspect-ratio.js +6 -0
- package/dist/components/attachment.d.ts +118 -0
- package/dist/components/attachment.js +285 -0
- package/dist/components/avatar.d.ts +32 -0
- package/dist/components/avatar.js +73 -0
- package/dist/components/badge.d.ts +21 -0
- package/dist/components/badge.js +27 -0
- package/dist/components/brand-icon.d.ts +28 -0
- package/dist/components/brand-icon.js +82 -0
- package/dist/components/breadcrumb.d.ts +29 -0
- package/dist/components/breadcrumb.js +44 -0
- package/dist/components/button-group.d.ts +28 -0
- package/dist/components/button-group.js +36 -0
- package/dist/components/button.d.ts +79 -0
- package/dist/components/button.js +93 -0
- package/dist/components/calendar.d.ts +27 -0
- package/dist/components/calendar.js +107 -0
- package/dist/components/canvas.d.ts +78 -0
- package/dist/components/canvas.js +180 -0
- package/dist/components/card.d.ts +43 -0
- package/dist/components/card.js +42 -0
- package/dist/components/carousel.d.ts +57 -0
- package/dist/components/carousel.js +422 -0
- package/dist/components/chart.d.ts +112 -0
- package/dist/components/chart.js +172 -0
- package/dist/components/chat-confirmation.d.ts +42 -0
- package/dist/components/chat-confirmation.js +62 -0
- package/dist/components/chat-input.d.ts +94 -0
- package/dist/components/chat-input.js +279 -0
- package/dist/components/chat-plan.d.ts +21 -0
- package/dist/components/chat-plan.js +35 -0
- package/dist/components/chat-sources.d.ts +64 -0
- package/dist/components/chat-sources.js +66 -0
- package/dist/components/checkbox.d.ts +16 -0
- package/dist/components/checkbox.js +17 -0
- package/dist/components/choice-list.d.ts +53 -0
- package/dist/components/choice-list.js +36 -0
- package/dist/components/code-block.d.ts +35 -0
- package/dist/components/code-block.js +64 -0
- package/dist/components/collapsible.d.ts +24 -0
- package/dist/components/collapsible.js +27 -0
- package/dist/components/combobox.d.ts +95 -0
- package/dist/components/combobox.js +106 -0
- package/dist/components/command.d.ts +94 -0
- package/dist/components/command.js +414 -0
- package/dist/components/context-menu.d.ts +57 -0
- package/dist/components/context-menu.js +60 -0
- package/dist/components/dialog.d.ts +60 -0
- package/dist/components/dialog.js +79 -0
- package/dist/components/direction.d.ts +6 -0
- package/dist/components/direction.js +6 -0
- package/dist/components/dropdown-menu.d.ts +62 -0
- package/dist/components/dropdown-menu.js +59 -0
- package/dist/components/field.d.ts +117 -0
- package/dist/components/field.js +150 -0
- package/dist/components/file-explorer.d.ts +27 -0
- package/dist/components/file-explorer.js +300 -0
- package/dist/components/file-viewer.d.ts +137 -0
- package/dist/components/file-viewer.js +400 -0
- package/dist/components/form.d.ts +87 -0
- package/dist/components/form.js +55 -0
- package/dist/components/glass-banner.d.ts +27 -0
- package/dist/components/glass-banner.js +116 -0
- package/dist/components/gradient-banner.d.ts +37 -0
- package/dist/components/gradient-banner.js +43 -0
- package/dist/components/hover-card.d.ts +27 -0
- package/dist/components/hover-card.js +26 -0
- package/dist/components/input-group.d.ts +68 -0
- package/dist/components/input-group.js +88 -0
- package/dist/components/input-otp.d.ts +38 -0
- package/dist/components/input-otp.js +43 -0
- package/dist/components/input.d.ts +16 -0
- package/dist/components/input.js +18 -0
- package/dist/components/kbd.d.ts +12 -0
- package/dist/components/kbd.js +11 -0
- package/dist/components/label.d.ts +8 -0
- package/dist/components/label.js +10 -0
- package/dist/components/mention-list.d.ts +54 -0
- package/dist/components/mention-list.js +31 -0
- package/dist/components/menubar.d.ts +64 -0
- package/dist/components/menubar.js +68 -0
- package/dist/components/message-actions.d.ts +34 -0
- package/dist/components/message-actions.js +68 -0
- package/dist/components/message-response.d.ts +20 -0
- package/dist/components/message-response.js +46 -0
- package/dist/components/message-scroller.d.ts +71 -0
- package/dist/components/message-scroller.js +217 -0
- package/dist/components/message.d.ts +49 -0
- package/dist/components/message.js +59 -0
- package/dist/components/navigation-menu.d.ts +45 -0
- package/dist/components/navigation-menu.js +54 -0
- package/dist/components/notice-banner.d.ts +14 -0
- package/dist/components/notice-banner.js +11 -0
- package/dist/components/page-content.d.ts +7 -0
- package/dist/components/page-content.js +9 -0
- package/dist/components/page-header.d.ts +37 -0
- package/dist/components/page-header.js +53 -0
- package/dist/components/pagination.d.ts +29 -0
- package/dist/components/pagination.js +29 -0
- package/dist/components/plan-outline.d.ts +60 -0
- package/dist/components/plan-outline.js +108 -0
- package/dist/components/popover.d.ts +34 -0
- package/dist/components/popover.js +35 -0
- package/dist/components/progress.d.ts +27 -0
- package/dist/components/progress.js +35 -0
- package/dist/components/prompt-list.d.ts +23 -0
- package/dist/components/prompt-list.js +11 -0
- package/dist/components/questionnaire.d.ts +148 -0
- package/dist/components/questionnaire.js +1002 -0
- package/dist/components/radio-group.d.ts +16 -0
- package/dist/components/radio-group.js +21 -0
- package/dist/components/reasoning-effort.d.ts +24 -0
- package/dist/components/reasoning-effort.js +269 -0
- package/dist/components/resizable.d.ts +67 -0
- package/dist/components/resizable.js +536 -0
- package/dist/components/result-card.d.ts +58 -0
- package/dist/components/result-card.js +88 -0
- package/dist/components/review-card.d.ts +62 -0
- package/dist/components/review-card.js +49 -0
- package/dist/components/rich-text-editor.d.ts +51 -0
- package/dist/components/rich-text-editor.js +248 -0
- package/dist/components/schema-diagram.d.ts +52 -0
- package/dist/components/schema-diagram.js +226 -0
- package/dist/components/scroll-area.d.ts +22 -0
- package/dist/components/scroll-area.js +19 -0
- package/dist/components/search-dialog.d.ts +103 -0
- package/dist/components/search-dialog.js +65 -0
- package/dist/components/search-field.d.ts +17 -0
- package/dist/components/search-field.js +27 -0
- package/dist/components/select.d.ts +57 -0
- package/dist/components/select.js +57 -0
- package/dist/components/selection-slot.d.ts +18 -0
- package/dist/components/selection-slot.js +53 -0
- package/dist/components/separator.d.ts +48 -0
- package/dist/components/separator.js +54 -0
- package/dist/components/settings-list.d.ts +26 -0
- package/dist/components/settings-list.js +17 -0
- package/dist/components/sheet.d.ts +70 -0
- package/dist/components/sheet.js +77 -0
- package/dist/components/sidebar.d.ts +70 -0
- package/dist/components/sidebar.js +187 -0
- package/dist/components/skeleton.d.ts +31 -0
- package/dist/components/skeleton.js +32 -0
- package/dist/components/slider.d.ts +17 -0
- package/dist/components/slider.js +24 -0
- package/dist/components/spinner.d.ts +37 -0
- package/dist/components/spinner.js +43 -0
- package/dist/components/state-panel-samples.d.ts +25 -0
- package/dist/components/state-panel-samples.js +423 -0
- package/dist/components/state-panel.d.ts +35 -0
- package/dist/components/state-panel.js +29 -0
- package/dist/components/state-sample.d.ts +63 -0
- package/dist/components/state-sample.js +42 -0
- package/dist/components/switch.d.ts +12 -0
- package/dist/components/switch.js +14 -0
- package/dist/components/table-nuqs.d.ts +16 -0
- package/dist/components/table-nuqs.js +59 -0
- package/dist/components/table.d.ts +120 -0
- package/dist/components/table.js +663 -0
- package/dist/components/tabs.d.ts +37 -0
- package/dist/components/tabs.js +36 -0
- package/dist/components/textarea.d.ts +18 -0
- package/dist/components/textarea.js +19 -0
- package/dist/components/toast.d.ts +45 -0
- package/dist/components/toast.js +83 -0
- package/dist/components/toggle-group.d.ts +30 -0
- package/dist/components/toggle-group.js +39 -0
- package/dist/components/toggle.d.ts +27 -0
- package/dist/components/toggle.js +21 -0
- package/dist/components/tool-activity.d.ts +79 -0
- package/dist/components/tool-activity.js +226 -0
- package/dist/components/toolbar-status.d.ts +21 -0
- package/dist/components/toolbar-status.js +14 -0
- package/dist/components/tooltip.d.ts +31 -0
- package/dist/components/tooltip.js +99 -0
- package/dist/components/typed-text.d.ts +17 -0
- package/dist/components/typed-text.js +56 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +90 -0
- package/dist/internal/brand-glyphs.d.ts +370 -0
- package/dist/internal/brand-glyphs.js +262 -0
- package/dist/internal/classes.d.ts +3 -0
- package/dist/internal/classes.js +20 -0
- package/dist/internal/code-tokens.d.ts +4 -0
- package/dist/internal/code-tokens.js +34 -0
- package/dist/internal/fieldset.d.ts +6 -0
- package/dist/internal/fieldset.js +38 -0
- package/dist/internal/file-kind.d.ts +4 -0
- package/dist/internal/file-kind.js +62 -0
- package/dist/internal/languages.d.ts +7 -0
- package/dist/internal/languages.js +1010 -0
- package/dist/internal/message-scroller.d.ts +102 -0
- package/dist/internal/message-scroller.js +637 -0
- package/dist/internal/pressable.d.ts +2 -0
- package/dist/internal/pressable.js +29 -0
- package/dist/internal/syntax.d.ts +14 -0
- package/dist/internal/syntax.js +137 -0
- package/dist/styles/accordion.css +102 -0
- package/dist/styles/alert-dialog.css +198 -0
- package/dist/styles/alert.css +149 -0
- package/dist/styles/aspect-ratio.css +8 -0
- package/dist/styles/attachment.css +700 -0
- package/dist/styles/avatar.css +186 -0
- package/dist/styles/badge.css +178 -0
- package/dist/styles/brand-icon.css +104 -0
- package/dist/styles/breadcrumb.css +121 -0
- package/dist/styles/button-group.css +113 -0
- package/dist/styles/button.css +380 -0
- package/dist/styles/calendar.css +295 -0
- package/dist/styles/canvas.css +313 -0
- package/dist/styles/card.css +146 -0
- package/dist/styles/carousel.css +107 -0
- package/dist/styles/chart.css +227 -0
- package/dist/styles/chat-confirmation.css +29 -0
- package/dist/styles/chat-input.css +240 -0
- package/dist/styles/chat-plan.css +109 -0
- package/dist/styles/chat-sources.css +191 -0
- package/dist/styles/checkbox.css +122 -0
- package/dist/styles/choice-list.css +343 -0
- package/dist/styles/code-block.css +211 -0
- package/dist/styles/collapsible.css +72 -0
- package/dist/styles/combobox.css +347 -0
- package/dist/styles/command.css +233 -0
- package/dist/styles/context-menu.css +151 -0
- package/dist/styles/dialog.css +237 -0
- package/dist/styles/dropdown-menu.css +150 -0
- package/dist/styles/field.css +340 -0
- package/dist/styles/file-explorer.css +517 -0
- package/dist/styles/file-viewer.css +375 -0
- package/dist/styles/form.css +155 -0
- package/dist/styles/glass-banner.css +308 -0
- package/dist/styles/gradient-banner.css +256 -0
- package/dist/styles/hover-card.css +95 -0
- package/dist/styles/index.css +268 -0
- package/dist/styles/input-group.css +285 -0
- package/dist/styles/input-otp.css +138 -0
- package/dist/styles/input.css +98 -0
- package/dist/styles/kbd.css +59 -0
- package/dist/styles/label.css +89 -0
- package/dist/styles/mention-list.css +166 -0
- package/dist/styles/menubar.css +226 -0
- package/dist/styles/message-actions.css +83 -0
- package/dist/styles/message-response.css +176 -0
- package/dist/styles/message-scroller.css +136 -0
- package/dist/styles/message.css +310 -0
- package/dist/styles/navigation-menu.css +291 -0
- package/dist/styles/notice-banner.css +68 -0
- package/dist/styles/page-content.css +21 -0
- package/dist/styles/page-header.css +156 -0
- package/dist/styles/pagination.css +72 -0
- package/dist/styles/plan-outline.css +393 -0
- package/dist/styles/popover.css +140 -0
- package/dist/styles/progress.css +93 -0
- package/dist/styles/prompt-list.css +165 -0
- package/dist/styles/questionnaire.css +308 -0
- package/dist/styles/radio-group.css +119 -0
- package/dist/styles/reasoning-effort.css +157 -0
- package/dist/styles/resizable.css +108 -0
- package/dist/styles/result-card.css +215 -0
- package/dist/styles/review-card.css +235 -0
- package/dist/styles/rich-text-editor.css +169 -0
- package/dist/styles/schema-diagram.css +419 -0
- package/dist/styles/scroll-area.css +77 -0
- package/dist/styles/search-dialog.css +280 -0
- package/dist/styles/search-field.css +17 -0
- package/dist/styles/select.css +285 -0
- package/dist/styles/selection-slot.css +31 -0
- package/dist/styles/separator.css +206 -0
- package/dist/styles/settings-list.css +81 -0
- package/dist/styles/sheet.css +361 -0
- package/dist/styles/sidebar.css +363 -0
- package/dist/styles/skeleton.css +80 -0
- package/dist/styles/slider.css +142 -0
- package/dist/styles/spinner.css +39 -0
- package/dist/styles/state-panel-samples.css +41 -0
- package/dist/styles/state-panel.css +173 -0
- package/dist/styles/state-sample.css +285 -0
- package/dist/styles/switch.css +128 -0
- package/dist/styles/table.css +604 -0
- package/dist/styles/tabs.css +223 -0
- package/dist/styles/textarea.css +77 -0
- package/dist/styles/themes/chatgpt.css +125 -0
- package/dist/styles/themes/claude.css +125 -0
- package/dist/styles/themes/cloudflare.css +125 -0
- package/dist/styles/themes/discord.css +125 -0
- package/dist/styles/themes/github.css +125 -0
- package/dist/styles/themes/grok.css +125 -0
- package/dist/styles/themes/linear.css +126 -0
- package/dist/styles/themes/neutral.css +125 -0
- package/dist/styles/themes/notion.css +125 -0
- package/dist/styles/themes/perplexity.css +125 -0
- package/dist/styles/themes/raycast.css +125 -0
- package/dist/styles/themes/spotify.css +125 -0
- package/dist/styles/themes/stripe.css +125 -0
- package/dist/styles/themes/supabase.css +125 -0
- package/dist/styles/themes/tulipfarm.css +115 -0
- package/dist/styles/themes/vercel.css +126 -0
- package/dist/styles/toast.css +197 -0
- package/dist/styles/toggle-group.css +161 -0
- package/dist/styles/toggle.css +119 -0
- package/dist/styles/tool-activity.css +396 -0
- package/dist/styles/toolbar-status.css +52 -0
- package/dist/styles/tooltip.css +122 -0
- package/dist/styles/typed-text.css +17 -0
- package/package.json +452 -4
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
import { type SeparatorLineProps } from "./separator.js";
|
|
4
|
+
export declare const buttonGroupOrientationNames: readonly ["horizontal", "vertical"];
|
|
5
|
+
export type ButtonGroupOrientation = (typeof buttonGroupOrientationNames)[number];
|
|
6
|
+
export interface ButtonGroupVariantOptions {
|
|
7
|
+
orientation?: ButtonGroupOrientation | null;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function buttonGroupVariants({ orientation, className, }?: ButtonGroupVariantOptions): string;
|
|
11
|
+
export interface ButtonGroupProps extends ComponentProps<"div"> {
|
|
12
|
+
/** `"vertical"` stacks the controls and joins them top to bottom. */
|
|
13
|
+
orientation?: ButtonGroupOrientation | null;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Joins buttons, text, and separators into one control. Give it an
|
|
17
|
+
* `aria-label` that names what the buttons act on.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ButtonGroup({ className, orientation, ...props }: ButtonGroupProps): import("react").JSX.Element;
|
|
20
|
+
export type ButtonGroupTextProps = useRender.ComponentProps<"div">;
|
|
21
|
+
/** A muted, non-interactive cell for a label or value inside a group. */
|
|
22
|
+
export declare function ButtonGroupText({ className, render, ref, ...props }: ButtonGroupTextProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
23
|
+
export type ButtonGroupSeparatorProps = SeparatorLineProps;
|
|
24
|
+
/**
|
|
25
|
+
* A line between grouped controls. It runs across the group, so it is
|
|
26
|
+
* `"vertical"` (the default) in a row and `"horizontal"` in a column.
|
|
27
|
+
*/
|
|
28
|
+
export declare function ButtonGroupSeparator({ className, orientation, tone, ...props }: ButtonGroupSeparatorProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
5
|
+
import { cn } from "../internal/classes.js";
|
|
6
|
+
import { Separator } from "./separator.js";
|
|
7
|
+
export const buttonGroupOrientationNames = ["horizontal", "vertical"];
|
|
8
|
+
export function buttonGroupVariants({ orientation, className, } = {}) {
|
|
9
|
+
return cn("sprout-button-group", `sprout-button-group-${orientation ?? "horizontal"}`, className);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Joins buttons, text, and separators into one control. Give it an
|
|
13
|
+
* `aria-label` that names what the buttons act on.
|
|
14
|
+
*/
|
|
15
|
+
export function ButtonGroup({ className, orientation, ...props }) {
|
|
16
|
+
return (_jsx("div", { role: "group", "data-slot": "button-group", "data-orientation": orientation ?? "horizontal", className: buttonGroupVariants({ orientation, className }), ...props }));
|
|
17
|
+
}
|
|
18
|
+
/** A muted, non-interactive cell for a label or value inside a group. */
|
|
19
|
+
export function ButtonGroupText({ className, render, ref, ...props }) {
|
|
20
|
+
return useRender({
|
|
21
|
+
defaultTagName: "div",
|
|
22
|
+
ref,
|
|
23
|
+
render,
|
|
24
|
+
props: mergeProps({
|
|
25
|
+
"data-slot": "button-group-text",
|
|
26
|
+
className: cn("sprout-button-group-text", className),
|
|
27
|
+
}, props),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A line between grouped controls. It runs across the group, so it is
|
|
32
|
+
* `"vertical"` (the default) in a row and `"horizontal"` in a column.
|
|
33
|
+
*/
|
|
34
|
+
export function ButtonGroupSeparator({ className, orientation = "vertical", tone = "input", ...props }) {
|
|
35
|
+
return (_jsx(Separator, { "data-slot": "button-group-separator", orientation: orientation, tone: tone, className: (state) => cn("sprout-button-group-separator", typeof className === "function" ? className(state) : className), ...props }));
|
|
36
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Button as BaseButton } from "@base-ui/react/button";
|
|
2
|
+
import { type ReactElement } from "react";
|
|
3
|
+
export declare const buttonVariantNames: readonly ["default", "outline", "secondary", "ghost", "destructive", "link", "scope"];
|
|
4
|
+
export declare const buttonSizeNames: readonly ["default", "xs", "sm", "lg", "icon", "icon-xs", "icon-sm", "icon-lg", "back", "onboarding", "bud", "bud-labeled", "chat-row", "task-link", "card-title", "result-card", "calendar-title", "calendar", "calendar-icon", "nav", "column-header", "column-header-end", "table-link", "badge"];
|
|
5
|
+
export type ButtonVariant = (typeof buttonVariantNames)[number];
|
|
6
|
+
export type ButtonSize = (typeof buttonSizeNames)[number];
|
|
7
|
+
export interface ButtonVariantOptions {
|
|
8
|
+
variant?: ButtonVariant | null;
|
|
9
|
+
size?: ButtonSize | null;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function buttonVariants({ variant, size, className, }?: ButtonVariantOptions): string;
|
|
13
|
+
export type ButtonBlockerFix =
|
|
14
|
+
/** An element id on this page; scrolled into view and focused (or, when it has no tabindex, its first focusable descendant). */
|
|
15
|
+
{
|
|
16
|
+
kind: "jump";
|
|
17
|
+
label: string;
|
|
18
|
+
target: string;
|
|
19
|
+
}
|
|
20
|
+
/** A link element, such as a router `<Link>`, rendered through Button `render`. */
|
|
21
|
+
| {
|
|
22
|
+
kind: "link";
|
|
23
|
+
label: string;
|
|
24
|
+
link: ReactElement;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "action";
|
|
27
|
+
label: string;
|
|
28
|
+
onClick: () => void;
|
|
29
|
+
};
|
|
30
|
+
export type ButtonState = {
|
|
31
|
+
kind: "ready";
|
|
32
|
+
} | {
|
|
33
|
+
kind: "busy";
|
|
34
|
+
} | {
|
|
35
|
+
kind: "blocked";
|
|
36
|
+
reason: string;
|
|
37
|
+
fix?: ButtonBlockerFix | undefined;
|
|
38
|
+
};
|
|
39
|
+
export interface ButtonProps extends BaseButton.Props {
|
|
40
|
+
variant?: ButtonVariant | null;
|
|
41
|
+
size?: ButtonSize | null;
|
|
42
|
+
/**
|
|
43
|
+
* Wraps the button in `data-slot="button-action"`, which also holds the
|
|
44
|
+
* blocked reason; `className` then styles that wrapper. Busy and blocked
|
|
45
|
+
* stay focusable. Keep passing a state (even `ready`) for the button's
|
|
46
|
+
* lifetime so changing kinds never remounts it.
|
|
47
|
+
*/
|
|
48
|
+
state?: ButtonState | undefined;
|
|
49
|
+
/** Replaces the label, with a spinner, while `state` is busy. */
|
|
50
|
+
pendingLabel?: string | undefined;
|
|
51
|
+
}
|
|
52
|
+
export declare function Button({ state, pendingLabel, ...props }: ButtonProps): import("react").JSX.Element;
|
|
53
|
+
export interface ActionGroupItem {
|
|
54
|
+
label: string;
|
|
55
|
+
onClick: () => void;
|
|
56
|
+
variant?: ButtonVariant | undefined;
|
|
57
|
+
}
|
|
58
|
+
export interface ActionGroupProps {
|
|
59
|
+
/** One state for every action, with one shared reason under the row. */
|
|
60
|
+
state: ButtonState;
|
|
61
|
+
actions: readonly ActionGroupItem[];
|
|
62
|
+
className?: string | undefined;
|
|
63
|
+
}
|
|
64
|
+
/** A row of actions that share one busy or blocked state and one reason. */
|
|
65
|
+
export declare function ActionGroup({ state, actions, className }: ActionGroupProps): import("react").JSX.Element;
|
|
66
|
+
export declare function jumpTo(target: string | Element): void;
|
|
67
|
+
/**
|
|
68
|
+
* Busy and blocked actions stay focusable so the reason is reachable.
|
|
69
|
+
* Absent ARIA keys are left out so spreading `button` keeps a caller's own.
|
|
70
|
+
*/
|
|
71
|
+
export declare function useActionControl(state: ButtonState): {
|
|
72
|
+
button: {
|
|
73
|
+
"aria-describedby"?: string | undefined;
|
|
74
|
+
"aria-busy"?: true | undefined;
|
|
75
|
+
disabled: boolean;
|
|
76
|
+
focusableWhenDisabled: boolean;
|
|
77
|
+
};
|
|
78
|
+
reason: import("react").JSX.Element | null;
|
|
79
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import { cn, stateClassName } from "../internal/classes.js";
|
|
5
|
+
import { Pressable } from "../internal/pressable.js";
|
|
6
|
+
import { Spinner } from "./spinner.js";
|
|
7
|
+
export const buttonVariantNames = [
|
|
8
|
+
"default",
|
|
9
|
+
"outline",
|
|
10
|
+
"secondary",
|
|
11
|
+
"ghost",
|
|
12
|
+
"destructive",
|
|
13
|
+
"link",
|
|
14
|
+
"scope",
|
|
15
|
+
];
|
|
16
|
+
export const buttonSizeNames = [
|
|
17
|
+
"default",
|
|
18
|
+
"xs",
|
|
19
|
+
"sm",
|
|
20
|
+
"lg",
|
|
21
|
+
"icon",
|
|
22
|
+
"icon-xs",
|
|
23
|
+
"icon-sm",
|
|
24
|
+
"icon-lg",
|
|
25
|
+
"back",
|
|
26
|
+
"onboarding",
|
|
27
|
+
"bud",
|
|
28
|
+
"bud-labeled",
|
|
29
|
+
"chat-row",
|
|
30
|
+
"task-link",
|
|
31
|
+
"card-title",
|
|
32
|
+
"result-card",
|
|
33
|
+
"calendar-title",
|
|
34
|
+
"calendar",
|
|
35
|
+
"calendar-icon",
|
|
36
|
+
"nav",
|
|
37
|
+
"column-header",
|
|
38
|
+
"column-header-end",
|
|
39
|
+
"table-link",
|
|
40
|
+
"badge",
|
|
41
|
+
];
|
|
42
|
+
export function buttonVariants({ variant, size, className, } = {}) {
|
|
43
|
+
return cn("sprout-button", `sprout-button-variant-${variant ?? "default"}`, `sprout-button-size-${size ?? "default"}`, className);
|
|
44
|
+
}
|
|
45
|
+
export function Button({ state, pendingLabel, ...props }) {
|
|
46
|
+
return state ? (_jsx(StatefulButton, { state: state, pendingLabel: pendingLabel, ...props })) : (_jsx(ButtonElement, { ...props }));
|
|
47
|
+
}
|
|
48
|
+
function ButtonElement({ variant, size, className, ...props }) {
|
|
49
|
+
return (_jsx(Pressable, { "data-slot": "button", "data-variant": variant ?? "default", "data-size": size ?? "default", ...props, className: stateClassName(buttonVariants({ variant, size }), className) }));
|
|
50
|
+
}
|
|
51
|
+
function StatefulButton({ state, pendingLabel, className, children, ...props }) {
|
|
52
|
+
const action = useActionControl(state);
|
|
53
|
+
const pending = state.kind === "busy" && pendingLabel;
|
|
54
|
+
return (_jsxs("div", { "data-slot": "button-action", "data-state": state.kind, className: cn("sprout-button-action", className), children: [_jsx(ButtonElement, { ...props, ...action.button, disabled: action.button.disabled || props.disabled, children: pending ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { "aria-hidden": "true" }), pendingLabel] })) : (children) }), action.reason] }));
|
|
55
|
+
}
|
|
56
|
+
/** A row of actions that share one busy or blocked state and one reason. */
|
|
57
|
+
export function ActionGroup({ state, actions, className }) {
|
|
58
|
+
const action = useActionControl(state);
|
|
59
|
+
return (_jsxs("div", { "data-slot": "action-group", "data-state": state.kind, className: cn("sprout-action-group", className), children: [_jsx("div", { className: "sprout-action-group-row", children: actions.map((item) => (_jsx(Button, { variant: item.variant, onClick: item.onClick, ...action.button, children: item.label }, item.label))) }), action.reason] }));
|
|
60
|
+
}
|
|
61
|
+
const focusable = ':is(a[href], button, input, select, textarea, [tabindex]):not([disabled], [tabindex="-1"], [type="hidden"])';
|
|
62
|
+
export function jumpTo(target) {
|
|
63
|
+
const element = typeof target === "string" ? document.getElementById(target) : target;
|
|
64
|
+
if (!element)
|
|
65
|
+
return;
|
|
66
|
+
const control = element.matches(focusable) || element.hasAttribute("tabindex")
|
|
67
|
+
? element
|
|
68
|
+
: element.querySelector(focusable);
|
|
69
|
+
(control ?? element).scrollIntoView({ block: "center" });
|
|
70
|
+
if (control instanceof HTMLElement)
|
|
71
|
+
control.focus({ preventScroll: true });
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Busy and blocked actions stay focusable so the reason is reachable.
|
|
75
|
+
* Absent ARIA keys are left out so spreading `button` keeps a caller's own.
|
|
76
|
+
*/
|
|
77
|
+
export function useActionControl(state) {
|
|
78
|
+
const reasonId = useId();
|
|
79
|
+
return {
|
|
80
|
+
button: {
|
|
81
|
+
disabled: state.kind !== "ready",
|
|
82
|
+
focusableWhenDisabled: true,
|
|
83
|
+
...(state.kind === "busy" && { "aria-busy": true }),
|
|
84
|
+
...(state.kind === "blocked" && { "aria-describedby": reasonId }),
|
|
85
|
+
},
|
|
86
|
+
reason: state.kind === "blocked" ? (_jsxs("p", { "data-slot": "action-reason", className: "sprout-action-reason", children: [_jsx("span", { id: reasonId, children: state.reason }), state.fix && _jsx(FixButton, { fix: state.fix })] })) : null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function FixButton({ fix }) {
|
|
90
|
+
if (fix.kind === "link")
|
|
91
|
+
return (_jsx(Button, { variant: "link", size: "xs", nativeButton: false, render: fix.link, children: fix.label }));
|
|
92
|
+
return (_jsx(Button, { variant: "link", size: "xs", onClick: fix.kind === "jump" ? () => jumpTo(fix.target) : fix.onClick, children: fix.label }));
|
|
93
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ComponentProps } from "react";
|
|
2
|
+
import { DayPicker, type DayButtonProps } from "react-day-picker";
|
|
3
|
+
import { type ButtonVariant } from "./button.js";
|
|
4
|
+
export type { DateRange, Matcher } from "react-day-picker";
|
|
5
|
+
export declare const calendarCaptionLayoutNames: readonly ["label", "dropdown", "dropdown-months", "dropdown-years"];
|
|
6
|
+
export type CalendarCaptionLayout = (typeof calendarCaptionLayoutNames)[number];
|
|
7
|
+
export type CalendarProps = ComponentProps<typeof DayPicker> & {
|
|
8
|
+
/** The Button variant for the previous and next month buttons. Defaults to `ghost`. */
|
|
9
|
+
buttonVariant?: ButtonVariant;
|
|
10
|
+
};
|
|
11
|
+
type DayPickerLocale = ComponentProps<typeof DayPicker>["locale"];
|
|
12
|
+
/**
|
|
13
|
+
* A month grid for picking one date, several dates, or a range. It takes every
|
|
14
|
+
* React DayPicker prop, including `mode`, `selected`, `onSelect`,
|
|
15
|
+
* `defaultMonth`, `disabled`, `weekStartsOn`, `showWeekNumber`, and
|
|
16
|
+
* `numberOfMonths`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function Calendar({ className, classNames, showOutsideDays, captionLayout, buttonVariant, locale, formatters, components, ...props }: CalendarProps): import("react").JSX.Element;
|
|
19
|
+
export type CalendarDayButtonProps = DayButtonProps & {
|
|
20
|
+
locale?: DayPickerLocale;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The button inside each day cell. Use it in `components.DayButton` to add
|
|
24
|
+
* content under the date, such as an event count in a `span`. `locale`
|
|
25
|
+
* defaults to the calendar's locale.
|
|
26
|
+
*/
|
|
27
|
+
export declare function CalendarDayButton({ className, day, modifiers, locale, ...props }: CalendarDayButtonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, } from "react";
|
|
4
|
+
import { DayPicker, getDefaultClassNames, useDayPicker, } from "react-day-picker";
|
|
5
|
+
import { ChevronDown } from "reicon-react/icons/ChevronDown";
|
|
6
|
+
import { ChevronLeft } from "reicon-react/icons/ChevronLeft";
|
|
7
|
+
import { ChevronRight } from "reicon-react/icons/ChevronRight";
|
|
8
|
+
import { cn } from "../internal/classes.js";
|
|
9
|
+
import { Button, buttonVariants } from "./button.js";
|
|
10
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "./select.js";
|
|
11
|
+
export const calendarCaptionLayoutNames = [
|
|
12
|
+
"label",
|
|
13
|
+
"dropdown",
|
|
14
|
+
"dropdown-months",
|
|
15
|
+
"dropdown-years",
|
|
16
|
+
];
|
|
17
|
+
function CalendarRoot({ className, rootRef, ...props }) {
|
|
18
|
+
return (_jsx("div", { "data-slot": "calendar", ref: rootRef, className: className, ...props }));
|
|
19
|
+
}
|
|
20
|
+
function CalendarChevron({ className, orientation, size: _size, disabled: _disabled, ...props }) {
|
|
21
|
+
const Icon = orientation === "left"
|
|
22
|
+
? ChevronLeft
|
|
23
|
+
: orientation === "right"
|
|
24
|
+
? ChevronRight
|
|
25
|
+
: ChevronDown;
|
|
26
|
+
return (_jsx(Icon, { "aria-hidden": "true", className: cn("sprout-calendar-chevron", className), ...props }));
|
|
27
|
+
}
|
|
28
|
+
function CalendarWeekNumber({ children, week: _week, ...props }) {
|
|
29
|
+
return (_jsx("th", { ...props, children: _jsx("div", { className: "sprout-calendar-week-number-cell", children: children }) }));
|
|
30
|
+
}
|
|
31
|
+
/** Month and year pickers use a Base UI select, never a native `<select>`. */
|
|
32
|
+
function CalendarDropdown({ options = [], value, onChange, disabled, className, style, "aria-label": label, }) {
|
|
33
|
+
const items = options.map((option) => ({
|
|
34
|
+
value: option.value,
|
|
35
|
+
label: option.label,
|
|
36
|
+
}));
|
|
37
|
+
return (_jsxs(Select, { items: items, value: Number(value), disabled: disabled, onValueChange: (next) => {
|
|
38
|
+
if (next === null)
|
|
39
|
+
return;
|
|
40
|
+
onChange?.({
|
|
41
|
+
target: { value: String(next) },
|
|
42
|
+
});
|
|
43
|
+
}, children: [_jsx(SelectTrigger, { size: "sm", "aria-label": label, style: style, className: cn("sprout-calendar-dropdown", className), children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { className: "sprout-calendar-dropdown-content", children: options.map((option) => (_jsx(SelectItem, { value: option.value, disabled: option.disabled, children: option.label }, option.value))) })] }));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A month grid for picking one date, several dates, or a range. It takes every
|
|
47
|
+
* React DayPicker prop, including `mode`, `selected`, `onSelect`,
|
|
48
|
+
* `defaultMonth`, `disabled`, `weekStartsOn`, `showWeekNumber`, and
|
|
49
|
+
* `numberOfMonths`.
|
|
50
|
+
*/
|
|
51
|
+
export function Calendar({ className, classNames, showOutsideDays = true, captionLayout = "label", buttonVariant = "ghost", locale, formatters, components, ...props }) {
|
|
52
|
+
const defaults = getDefaultClassNames();
|
|
53
|
+
const navButton = cn(buttonVariants({ variant: buttonVariant, size: "icon-sm" }), "sprout-calendar-nav-button");
|
|
54
|
+
return (_jsx(DayPicker, { showOutsideDays: showOutsideDays, className: cn("sprout-calendar", className), "data-button-variant": buttonVariant, "data-caption-layout": captionLayout, captionLayout: captionLayout, locale: locale, formatters: {
|
|
55
|
+
formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: "short" }),
|
|
56
|
+
...formatters,
|
|
57
|
+
}, classNames: {
|
|
58
|
+
root: cn("sprout-calendar-root", defaults.root),
|
|
59
|
+
months: cn("sprout-calendar-months", defaults.months),
|
|
60
|
+
month: cn("sprout-calendar-month", defaults.month),
|
|
61
|
+
nav: cn("sprout-calendar-nav", defaults.nav),
|
|
62
|
+
button_previous: cn(navButton, defaults.button_previous),
|
|
63
|
+
button_next: cn(navButton, defaults.button_next),
|
|
64
|
+
month_caption: cn("sprout-calendar-caption", defaults.month_caption),
|
|
65
|
+
dropdowns: cn("sprout-calendar-dropdowns", defaults.dropdowns),
|
|
66
|
+
dropdown_root: cn("sprout-calendar-dropdown-root", defaults.dropdown_root),
|
|
67
|
+
dropdown: cn(defaults.dropdown),
|
|
68
|
+
caption_label: cn("sprout-calendar-caption-label", defaults.caption_label),
|
|
69
|
+
month_grid: cn("sprout-calendar-grid", defaults.month_grid),
|
|
70
|
+
weekdays: cn("sprout-calendar-weekdays", defaults.weekdays),
|
|
71
|
+
weekday: cn("sprout-calendar-weekday", defaults.weekday),
|
|
72
|
+
week: cn("sprout-calendar-week", defaults.week),
|
|
73
|
+
week_number_header: cn("sprout-calendar-week-number-header", defaults.week_number_header),
|
|
74
|
+
week_number: cn("sprout-calendar-week-number", defaults.week_number),
|
|
75
|
+
day: cn("sprout-calendar-day", defaults.day),
|
|
76
|
+
range_start: cn("sprout-calendar-range-start", defaults.range_start),
|
|
77
|
+
range_middle: cn("sprout-calendar-range-middle", defaults.range_middle),
|
|
78
|
+
range_end: cn("sprout-calendar-range-end", defaults.range_end),
|
|
79
|
+
today: cn("sprout-calendar-today", defaults.today),
|
|
80
|
+
outside: cn("sprout-calendar-outside", defaults.outside),
|
|
81
|
+
disabled: cn("sprout-calendar-disabled", defaults.disabled),
|
|
82
|
+
hidden: cn("sprout-calendar-hidden", defaults.hidden),
|
|
83
|
+
...classNames,
|
|
84
|
+
}, components: {
|
|
85
|
+
Root: CalendarRoot,
|
|
86
|
+
Chevron: CalendarChevron,
|
|
87
|
+
Dropdown: CalendarDropdown,
|
|
88
|
+
DayButton: CalendarDayButton,
|
|
89
|
+
WeekNumber: CalendarWeekNumber,
|
|
90
|
+
...components,
|
|
91
|
+
}, ...props }));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The button inside each day cell. Use it in `components.DayButton` to add
|
|
95
|
+
* content under the date, such as an event count in a `span`. `locale`
|
|
96
|
+
* defaults to the calendar's locale.
|
|
97
|
+
*/
|
|
98
|
+
export function CalendarDayButton({ className, day, modifiers, locale, ...props }) {
|
|
99
|
+
const calendarLocale = useDayPicker().dayPickerProps.locale;
|
|
100
|
+
const ref = useRef(null);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (modifiers.focused)
|
|
103
|
+
ref.current?.focus();
|
|
104
|
+
}, [modifiers.focused]);
|
|
105
|
+
const range = modifiers.range_start || modifiers.range_end || modifiers.range_middle;
|
|
106
|
+
return (_jsx(Button, { ref: ref, variant: "ghost", size: "icon", "data-slot": "calendar-day-button", "data-day": day.date.toLocaleDateString((locale ?? calendarLocale)?.code), "data-selected-single": Boolean(modifiers.selected && !range), "data-range-start": Boolean(modifiers.range_start), "data-range-end": Boolean(modifiers.range_end), "data-range-middle": Boolean(modifiers.range_middle), className: cn("sprout-calendar-day-button", getDefaultClassNames().day_button, className), ...props }));
|
|
107
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type ComponentType, type ReactNode } from "react";
|
|
2
|
+
/** Below this split width (px), the canvas opens as a full-screen sheet. */
|
|
3
|
+
export declare const canvasSplitMinWidth = 720;
|
|
4
|
+
export interface CanvasTab {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
icon?: ComponentType<{
|
|
8
|
+
size?: number;
|
|
9
|
+
}> | undefined;
|
|
10
|
+
/** The result is still loading. Shows a spinner and reads “loading”. */
|
|
11
|
+
busy?: boolean | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface ChatSplitProps {
|
|
14
|
+
open: boolean;
|
|
15
|
+
onOpenChange: (open: boolean) => void;
|
|
16
|
+
chat: ReactNode;
|
|
17
|
+
canvas: ReactNode;
|
|
18
|
+
/** Saves the person's split sizes under this key. Use one per screen. */
|
|
19
|
+
layoutId?: string | undefined;
|
|
20
|
+
className?: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Chat beside a resizable canvas. Under 768px viewports, or when the split is
|
|
24
|
+
* narrower than `canvasSplitMinWidth`, the canvas opens as a full-screen
|
|
25
|
+
* sheet. The canvas moves between both without remounting, so drafts,
|
|
26
|
+
* reviews, and scroll survive.
|
|
27
|
+
*/
|
|
28
|
+
export declare function ChatSplit({ open, onOpenChange, chat, canvas, layoutId, className, }: ChatSplitProps): import("react").JSX.Element;
|
|
29
|
+
export interface CanvasPanelProps {
|
|
30
|
+
/** Open results. With none, the header reads “Canvas”. */
|
|
31
|
+
tabs: readonly CanvasTab[];
|
|
32
|
+
activeId: string | null;
|
|
33
|
+
onSelect: (id: string) => void;
|
|
34
|
+
onCloseTab: (id: string) => void;
|
|
35
|
+
/** Hides the canvas. Called by Escape, the close button, and “Back to chat”. */
|
|
36
|
+
onClose: () => void;
|
|
37
|
+
children: ReactNode;
|
|
38
|
+
/** A strip under the tabs, such as “A Google action needs your review”. */
|
|
39
|
+
activity?: ReactNode;
|
|
40
|
+
className?: string | undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The canvas surface: a tab per open result, a close button, and the active
|
|
44
|
+
* result below. Arrow keys, Home, and End move between tabs; Delete closes
|
|
45
|
+
* the focused tab; Escape closes the canvas.
|
|
46
|
+
*/
|
|
47
|
+
export declare function CanvasPanel({ tabs, activeId, onSelect, onCloseTab, onClose, children, activity, className, }: CanvasPanelProps): import("react").JSX.Element;
|
|
48
|
+
export interface CanvasToggleProps {
|
|
49
|
+
open: boolean;
|
|
50
|
+
/** Open results. Shown as a badge when above zero. */
|
|
51
|
+
count: number;
|
|
52
|
+
onToggle: () => void;
|
|
53
|
+
className?: string | undefined;
|
|
54
|
+
}
|
|
55
|
+
/** A header button that shows or hides the canvas. */
|
|
56
|
+
export declare function CanvasToggle({ open, count, onToggle, className, }: CanvasToggleProps): import("react").JSX.Element;
|
|
57
|
+
export interface CanvasSectionProps {
|
|
58
|
+
title?: string | undefined;
|
|
59
|
+
/** A short line under the title, such as a row count, source, or URL. */
|
|
60
|
+
meta?: string | undefined;
|
|
61
|
+
/** Buttons beside the title. They wrap below it when space runs out. */
|
|
62
|
+
actions?: ReactNode;
|
|
63
|
+
children: ReactNode;
|
|
64
|
+
className?: string | undefined;
|
|
65
|
+
}
|
|
66
|
+
/** Padded content for one result, with an optional title, meta, and actions. */
|
|
67
|
+
export declare function CanvasSection({ title, meta, actions, children, className, }: CanvasSectionProps): import("react").JSX.Element;
|
|
68
|
+
export interface CanvasFrameProps {
|
|
69
|
+
src: string;
|
|
70
|
+
/** Names the frame for assistive tech, such as the page title. */
|
|
71
|
+
title: string;
|
|
72
|
+
className?: string | undefined;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A sandboxed page preview. Scripts and popups run; same-origin access,
|
|
76
|
+
* forms, and top navigation are blocked, and no referrer is sent.
|
|
77
|
+
*/
|
|
78
|
+
export declare function CanvasFrame({ src, title, className }: CanvasFrameProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore, } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
import { ArrowLeft } from "reicon-react/icons/ArrowLeft";
|
|
6
|
+
import { SidebarRight } from "reicon-react/icons/SidebarRight";
|
|
7
|
+
import { X } from "reicon-react/icons/X";
|
|
8
|
+
import { cn } from "../internal/classes.js";
|
|
9
|
+
import { Button } from "./button.js";
|
|
10
|
+
import { Spinner } from "./spinner.js";
|
|
11
|
+
import { ResizableHandle, ResizablePanel, ResizablePanelGroup, useResizableLayout, } from "./resizable.js";
|
|
12
|
+
import { Sheet, SheetContent, SheetDescription, SheetTitle } from "./sheet.js";
|
|
13
|
+
/** Below this split width (px), the canvas opens as a full-screen sheet. */
|
|
14
|
+
export const canvasSplitMinWidth = 720;
|
|
15
|
+
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
16
|
+
const mobileQuery = "(max-width: 767px)";
|
|
17
|
+
function subscribeMobile(callback) {
|
|
18
|
+
const media = window.matchMedia(mobileQuery);
|
|
19
|
+
media.addEventListener("change", callback);
|
|
20
|
+
return () => media.removeEventListener("change", callback);
|
|
21
|
+
}
|
|
22
|
+
function useViewportMobile() {
|
|
23
|
+
return useSyncExternalStore(subscribeMobile, () => window.matchMedia(mobileQuery).matches, () => false);
|
|
24
|
+
}
|
|
25
|
+
const CanvasNarrowContext = createContext(null);
|
|
26
|
+
/**
|
|
27
|
+
* Chat beside a resizable canvas. Under 768px viewports, or when the split is
|
|
28
|
+
* narrower than `canvasSplitMinWidth`, the canvas opens as a full-screen
|
|
29
|
+
* sheet. The canvas moves between both without remounting, so drafts,
|
|
30
|
+
* reviews, and scroll survive.
|
|
31
|
+
*/
|
|
32
|
+
export function ChatSplit({ open, onOpenChange, chat, canvas, layoutId = "chat-canvas", className, }) {
|
|
33
|
+
const viewportMobile = useViewportMobile();
|
|
34
|
+
const container = useRef(null);
|
|
35
|
+
const [availableWidth, setAvailableWidth] = useState(null);
|
|
36
|
+
const mobile = viewportMobile ||
|
|
37
|
+
(availableWidth !== null && availableWidth < canvasSplitMinWidth);
|
|
38
|
+
const split = open && !mobile;
|
|
39
|
+
useIsomorphicLayoutEffect(() => {
|
|
40
|
+
const element = container.current;
|
|
41
|
+
if (!element)
|
|
42
|
+
return;
|
|
43
|
+
const measure = () => setAvailableWidth(element.clientWidth);
|
|
44
|
+
measure();
|
|
45
|
+
const observer = new ResizeObserver(measure);
|
|
46
|
+
observer.observe(element);
|
|
47
|
+
return () => observer.disconnect();
|
|
48
|
+
}, []);
|
|
49
|
+
const layout = useResizableLayout({
|
|
50
|
+
id: layoutId,
|
|
51
|
+
panelIds: split ? ["chat", "canvas"] : ["chat"],
|
|
52
|
+
onlySaveAfterUserInteractions: true,
|
|
53
|
+
});
|
|
54
|
+
const [desktopHost, setDesktopHost] = useState(null);
|
|
55
|
+
const [mobileHost, setMobileHost] = useState(null);
|
|
56
|
+
const [target, setTarget] = useState(null);
|
|
57
|
+
useIsomorphicLayoutEffect(() => {
|
|
58
|
+
const element = document.createElement("div");
|
|
59
|
+
element.className = "sprout-canvas-host";
|
|
60
|
+
setTarget(element);
|
|
61
|
+
}, []);
|
|
62
|
+
useIsomorphicLayoutEffect(() => {
|
|
63
|
+
if (!target || !open)
|
|
64
|
+
return;
|
|
65
|
+
(mobile ? mobileHost : desktopHost)?.append(target);
|
|
66
|
+
return () => target.remove();
|
|
67
|
+
}, [mobile, open, target, mobileHost, desktopHost]);
|
|
68
|
+
return (_jsx(CanvasNarrowContext, { value: mobile, children: _jsxs("div", { ref: container, "data-slot": "chat-split", "data-layout": mobile ? "sheet" : "split", "data-open": open ? "" : undefined, className: cn("sprout-chat-split", className), children: [target && createPortal(canvas, target), _jsxs(ResizablePanelGroup, { orientation: "horizontal", defaultLayout: layout.defaultLayout, onLayoutChanged: layout.onLayoutChanged, children: [_jsx(ResizablePanel, { id: "chat", minSize: "40%", defaultSize: split ? "55%" : "100%", children: _jsx("div", { className: "sprout-chat-split-pane", children: chat }) }), split && (_jsxs(_Fragment, { children: [_jsx(ResizableHandle, { withHandle: true, "aria-label": "Resize canvas" }), _jsx(ResizablePanel, { id: "canvas", minSize: "320px", defaultSize: "45%", children: _jsx("div", { ref: setDesktopHost, className: "sprout-chat-split-pane" }) })] }))] }), _jsx(Sheet, { open: open && mobile, onOpenChange: (next) => onOpenChange(next), children: _jsxs(SheetContent, { side: "right", size: "full", showCloseButton: false, keepMounted: true, finalFocus: !open, className: "sprout-canvas-sheet", children: [_jsxs("div", { className: "sprout-canvas-sr-only", children: [_jsx(SheetTitle, { children: "Canvas" }), _jsx(SheetDescription, { children: "Detailed result. Return to chat to continue the conversation." })] }), _jsx("div", { ref: setMobileHost, className: "sprout-chat-split-pane" })] }) })] }) }));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The canvas surface: a tab per open result, a close button, and the active
|
|
72
|
+
* result below. Arrow keys, Home, and End move between tabs; Delete closes
|
|
73
|
+
* the focused tab; Escape closes the canvas.
|
|
74
|
+
*/
|
|
75
|
+
export function CanvasPanel({ tabs, activeId, onSelect, onCloseTab, onClose, children, activity, className, }) {
|
|
76
|
+
const viewportMobile = useViewportMobile();
|
|
77
|
+
const narrow = useContext(CanvasNarrowContext);
|
|
78
|
+
const mobile = narrow ?? viewportMobile;
|
|
79
|
+
const base = useId();
|
|
80
|
+
const list = useRef(null);
|
|
81
|
+
const tabButtons = useRef(new Map());
|
|
82
|
+
const closeButton = useRef(null);
|
|
83
|
+
const heading = useRef(null);
|
|
84
|
+
const refocus = useRef(null);
|
|
85
|
+
const [hovered, setHovered] = useState(null);
|
|
86
|
+
const activeIndex = tabs.findIndex((tab) => tab.id === activeId);
|
|
87
|
+
const tabId = (index) => `${base}-tab-${index}`;
|
|
88
|
+
const bodyId = `${base}-body`;
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const scroller = list.current;
|
|
91
|
+
const tab = activeId ? tabButtons.current.get(activeId) : null;
|
|
92
|
+
if (!scroller || !tab)
|
|
93
|
+
return;
|
|
94
|
+
const start = tab.offsetLeft;
|
|
95
|
+
const end = start + tab.offsetWidth + 28;
|
|
96
|
+
if (start < scroller.scrollLeft)
|
|
97
|
+
scroller.scrollLeft = start;
|
|
98
|
+
else if (end > scroller.scrollLeft + scroller.clientWidth)
|
|
99
|
+
scroller.scrollLeft = end - scroller.clientWidth;
|
|
100
|
+
}, [activeId, tabs.length]);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
const index = refocus.current;
|
|
103
|
+
if (index === null)
|
|
104
|
+
return;
|
|
105
|
+
refocus.current = null;
|
|
106
|
+
const next = tabs[Math.min(index, tabs.length - 1)];
|
|
107
|
+
const target = (next && tabButtons.current.get(next.id)) ??
|
|
108
|
+
closeButton.current ??
|
|
109
|
+
heading.current;
|
|
110
|
+
target?.focus();
|
|
111
|
+
}, [tabs]);
|
|
112
|
+
const closeTab = (index) => {
|
|
113
|
+
const tab = tabs[index];
|
|
114
|
+
if (!tab)
|
|
115
|
+
return;
|
|
116
|
+
refocus.current = index;
|
|
117
|
+
onCloseTab(tab.id);
|
|
118
|
+
};
|
|
119
|
+
const onTabKeyDown = (event, index) => {
|
|
120
|
+
if (event.key === "Delete") {
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
closeTab(index);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const next = event.key === "ArrowRight"
|
|
126
|
+
? index + 1
|
|
127
|
+
: event.key === "ArrowLeft"
|
|
128
|
+
? index - 1
|
|
129
|
+
: event.key === "Home"
|
|
130
|
+
? 0
|
|
131
|
+
: event.key === "End"
|
|
132
|
+
? tabs.length - 1
|
|
133
|
+
: null;
|
|
134
|
+
if (next === null || tabs.length === 0)
|
|
135
|
+
return;
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
const target = tabs[(next + tabs.length) % tabs.length];
|
|
138
|
+
if (!target)
|
|
139
|
+
return;
|
|
140
|
+
onSelect(target.id);
|
|
141
|
+
tabButtons.current.get(target.id)?.focus();
|
|
142
|
+
};
|
|
143
|
+
return (_jsxs("section", { "data-slot": "canvas", "data-layout": mobile ? "sheet" : "split", "aria-label": "Canvas", className: cn("sprout-canvas", className), onKeyDown: (event) => {
|
|
144
|
+
if (event.key === "Escape" && !event.defaultPrevented) {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
onClose();
|
|
147
|
+
}
|
|
148
|
+
}, children: [mobile && (_jsx("div", { className: "sprout-canvas-back", children: _jsxs(Button, { variant: "ghost", size: "sm", onClick: onClose, children: [_jsx(ArrowLeft, { size: 16, "aria-hidden": "true" }), "Back to chat"] }) })), _jsxs("header", { className: "sprout-canvas-header", children: [tabs.length === 0 ? (_jsx("h2", { ref: heading, tabIndex: -1, className: "sprout-canvas-heading", children: "Canvas" })) : (_jsxs("div", { ref: list, className: "sprout-canvas-tabs", children: [_jsx("div", { role: "tablist", "aria-label": "Open in canvas", className: "sprout-canvas-tablist", children: tabs.map((tab, index) => {
|
|
149
|
+
const active = index === activeIndex;
|
|
150
|
+
const Icon = tab.icon;
|
|
151
|
+
return (_jsxs("button", { type: "button", role: "tab", id: tabId(index), ref: (node) => {
|
|
152
|
+
if (node)
|
|
153
|
+
tabButtons.current.set(tab.id, node);
|
|
154
|
+
else
|
|
155
|
+
tabButtons.current.delete(tab.id);
|
|
156
|
+
}, "data-slot": "canvas-tab", "data-active": active ? "" : undefined, "data-busy": tab.busy ? "" : undefined, "data-hover": hovered === tab.id ? "" : undefined, "aria-label": tab.busy ? `${tab.title}, loading` : undefined, "aria-selected": active, "aria-controls": bodyId, tabIndex: active || (activeIndex === -1 && index === 0) ? 0 : -1, style: { order: index * 2 }, onClick: () => onSelect(tab.id), onKeyDown: (event) => onTabKeyDown(event, index), onPointerEnter: () => setHovered(tab.id), onPointerLeave: () => setHovered(null), className: "sprout-canvas-tab", children: [tab.busy ? (_jsx(Spinner, { size: "sm", "aria-hidden": "true" })) : (Icon && (_jsx("span", { "aria-hidden": "true", className: "sprout-canvas-tab-icon", children: _jsx(Icon, { size: 14 }) }))), _jsx("span", { className: "sprout-canvas-tab-title", children: tab.title })] }, tab.id));
|
|
157
|
+
}) }), tabs.map((tab, index) => {
|
|
158
|
+
const active = index === activeIndex;
|
|
159
|
+
return (_jsx("button", { type: "button", "aria-label": `Close ${tab.title}`, "data-active": active ? "" : undefined, "data-hover": hovered === tab.id ? "" : undefined, tabIndex: active ? 0 : -1, style: { order: index * 2 + 1 }, onClick: () => closeTab(index), onPointerEnter: () => setHovered(tab.id), onPointerLeave: () => setHovered(null), className: "sprout-canvas-tab-close", children: _jsx(X, { size: 12, "aria-hidden": "true" }) }, tab.id));
|
|
160
|
+
})] })), !mobile && (_jsx(Button, { ref: closeButton, variant: "ghost", size: "icon-sm", "aria-label": "Close canvas", title: "Close canvas (Esc)", onClick: onClose, children: _jsx(X, { size: 16, "aria-hidden": "true" }) }))] }), activity, _jsx("div", { id: bodyId, role: tabs.length ? "tabpanel" : undefined, "aria-labelledby": activeIndex === -1 ? undefined : tabId(activeIndex), tabIndex: tabs.length ? 0 : undefined, className: "sprout-canvas-body", children: children })] }));
|
|
161
|
+
}
|
|
162
|
+
/** A header button that shows or hides the canvas. */
|
|
163
|
+
export function CanvasToggle({ open, count, onToggle, className, }) {
|
|
164
|
+
return (_jsxs(Button, { variant: "ghost", size: "sm", "data-slot": "canvas-toggle", "aria-pressed": open, "aria-label": open
|
|
165
|
+
? "Hide canvas"
|
|
166
|
+
: count
|
|
167
|
+
? `Show canvas, ${count} open`
|
|
168
|
+
: "Show canvas", title: "Open detailed results without leaving your chat", onClick: onToggle, className: cn("sprout-canvas-toggle", className), children: [_jsx(SidebarRight, { size: 16, "aria-hidden": "true" }), _jsx("span", { className: "sprout-canvas-toggle-label", children: open ? "Hide results" : "View results" }), count > 0 && (_jsx("span", { "aria-hidden": "true", className: "sprout-canvas-toggle-count", children: count }))] }));
|
|
169
|
+
}
|
|
170
|
+
/** Padded content for one result, with an optional title, meta, and actions. */
|
|
171
|
+
export function CanvasSection({ title, meta, actions, children, className, }) {
|
|
172
|
+
return (_jsxs("div", { "data-slot": "canvas-section", className: cn("sprout-canvas-section", className), children: [(title || actions) && (_jsxs("div", { className: "sprout-canvas-section-header", children: [_jsxs("div", { className: "sprout-canvas-section-text", children: [title && _jsx("h3", { className: "sprout-canvas-section-title", children: title }), meta && _jsx("p", { className: "sprout-canvas-section-meta", children: meta })] }), actions && (_jsx("div", { className: "sprout-canvas-section-actions", children: actions }))] })), children] }));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A sandboxed page preview. Scripts and popups run; same-origin access,
|
|
176
|
+
* forms, and top navigation are blocked, and no referrer is sent.
|
|
177
|
+
*/
|
|
178
|
+
export function CanvasFrame({ src, title, className }) {
|
|
179
|
+
return (_jsx("div", { "data-slot": "canvas-frame", className: cn("sprout-canvas-frame", className), children: _jsx("iframe", { src: src, title: title, sandbox: "allow-scripts allow-popups", referrerPolicy: "no-referrer", loading: "lazy" }) }));
|
|
180
|
+
}
|