1import { mergeProps } from "@base-ui/react/merge-props";
2import { Select as SelectPrimitive } from "@base-ui/react/select";
3import { useRender } from "@base-ui/react/use-render";
4import type { ComponentProps, ComponentType, SVGProps } from "react";
5import { ChevronDownIcon } from "lucide-react";
6
7import { cn } from "~/lib/utils";
8import { Separator } from "../ui/separator";
9
10export type ComposerControlSize = "sm" | "xs";
11
12/**
13 * The composer toolbar's control look. `sm` is the expanded toolbar; `xs` is the dimmer resting
14 * strip. `aria-pressed` marks a toggle that is on (plan mode). This is an app control, not a
15 * restyled Button, so it owns its classes.
16 */
17function composerControlClassName(size: ComposerControlSize, className?: string) {
18 return cn(
19 "relative inline-flex shrink-0 cursor-pointer items-center justify-center whitespace-nowrap rounded-(--control-radius) border border-transparent text-base outline-none hover:bg-accent data-pressed:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 data-disabled:pointer-events-none data-disabled:opacity-64 pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&:active:not([aria-haspopup])]:scale-[0.97] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:-mx-0.5 [&_svg[data-composer-control-icon]]:mx-0",
20 size === "xs"
21 ? "h-7 gap-1 px-1.75 font-normal text-muted-foreground/70 text-sm hover:text-foreground/80 sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5 [&_svg[data-composer-control-chevron]]:ms-0 [&_svg[data-composer-control-chevron]]:-me-1"
22 : "h-7 gap-1.5 px-2.5 font-medium text-secondary-label [&_svg:not([class*='text-'])]:text-muted-foreground hover:text-foreground sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4",
23 "aria-pressed:bg-accent aria-pressed:text-accent-foreground aria-pressed:hover:bg-accent/80",
24 className,
25 );
26}
27
28type ComposerControlProps = useRender.ComponentProps<"button"> & {
29 size?: ComposerControlSize;
30};
31
32export function ComposerControl({
33 className,
34 size = "sm",
35 render,
36 ...props
37}: ComposerControlProps) {
38 const defaultProps = {
39 className: composerControlClassName(size, className),
40 type: render ? undefined : ("button" as const),
41 };
42 return useRender({
43 defaultTagName: "button",
44 props: mergeProps<"button">(defaultProps, props),
45 render,
46 });
47}
48
49export function ComposerControlIcon({
50 icon: Icon,
51 className,
52 opticalSize = "default",
53 size = "sm",
54}: {
55 icon: ComponentType<SVGProps<SVGSVGElement>>;
56 className?: string | undefined;
57 opticalSize?: "default" | "large";
58 size?: ComposerControlSize;
59}) {
60 return (
61 <Icon
62 aria-hidden="true"
63 className={cn(
64 "shrink-0",
65 size === "xs" ? "size-3" : opticalSize === "large" ? "size-4.5" : "size-4",
66 className,
67 )}
68 data-composer-control-icon
69 />
70 );
71}
72
73export function ComposerControlChevron({
74 className,
75 size = "sm",
76}: {
77 className?: string;
78 size?: ComposerControlSize;
79} = {}) {
80 return (
81 <ChevronDownIcon
82 aria-hidden="true"
83 className={cn(
84 "shrink-0",
85 size === "xs" ? "size-3 text-current opacity-50" : "size-3.5 text-icon-muted",
86 className,
87 )}
88 data-composer-control-chevron
89 strokeWidth={2.25}
90 />
91 );
92}
93
94export function ComposerControlSeparator({
95 className,
96 size = "sm",
97 ...props
98}: Omit<ComponentProps<typeof Separator>, "orientation"> & {
99 size?: ComposerControlSize;
100}) {
101 return (
102 <Separator
103 orientation="vertical"
104 className={cn("mx-0.5 hidden sm:block", size === "xs" ? "h-3.5!" : "h-4", className)}
105 {...props}
106 />
107 );
108}
109
110export function ComposerSelectControl({
111 className,
112 children,
113 size = "sm",
114 ...props
115}: Omit<SelectPrimitive.Trigger.Props, "className"> & {
116 className?: string | undefined;
117 size?: ComposerControlSize;
118}) {
119 return (
120 <SelectPrimitive.Trigger className={composerControlClassName(size, className)} {...props}>
121 {children}
122 <SelectPrimitive.Icon>
123 <ComposerControlChevron size={size} />
124 </SelectPrimitive.Icon>
125 </SelectPrimitive.Trigger>
126 );
127}