| 1 | import { mergeProps } from "@base-ui/react/merge-props"; |
| 2 | import { useRender } from "@base-ui/react/use-render"; |
| 3 | import type { ComponentProps } from "react"; |
| 4 | |
| 5 | import { cn } from "~/lib/utils"; |
| 6 | import { Button } from "../ui/button"; |
| 7 | import { |
| 8 | THREAD_DETAILS_PANEL_ROW_CLASS, |
| 9 | THREAD_DETAILS_PANEL_SELECT_ROW_CLASS, |
| 10 | THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS, |
| 11 | THREAD_DETAILS_PANEL_SPLIT_SECONDARY_CLASS, |
| 12 | THREAD_DETAILS_PANEL_ICON_ACTION_CLASS, |
| 13 | THREAD_DETAILS_PANEL_META_ACTION_CLASS, |
| 14 | THREAD_DETAILS_PANEL_CHEVRON_CLASS, |
| 15 | } from "./threadDetailsPanelStyles"; |
| 16 | import { ChevronDownIcon } from "lucide-react"; |
| 17 | |
| 18 | const parts = { |
| 19 | row: THREAD_DETAILS_PANEL_ROW_CLASS, |
| 20 | select: THREAD_DETAILS_PANEL_SELECT_ROW_CLASS, |
| 21 | primary: THREAD_DETAILS_PANEL_SPLIT_PRIMARY_CLASS, |
| 22 | secondary: THREAD_DETAILS_PANEL_SPLIT_SECONDARY_CLASS, |
| 23 | icon: THREAD_DETAILS_PANEL_ICON_ACTION_CLASS, |
| 24 | meta: THREAD_DETAILS_PANEL_META_ACTION_CLASS, |
| 25 | }; |
| 26 | |
| 27 | /** Panel controls own their fixed density; toolbar controls use the standard Button variants. */ |
| 28 | export function ThreadDetailsControl({ |
| 29 | panel = true, |
| 30 | part = "row", |
| 31 | multiline = false, |
| 32 | tone = "default", |
| 33 | className, |
| 34 | children, |
| 35 | size = "default", |
| 36 | variant = "default", |
| 37 | render, |
| 38 | ...props |
| 39 | }: ComponentProps<typeof Button> & { |
| 40 | panel?: boolean; |
| 41 | part?: keyof typeof parts; |
| 42 | multiline?: boolean; |
| 43 | tone?: "default" | "muted" | "primary" | "destructive"; |
| 44 | }) { |
| 45 | const control = useRender({ |
| 46 | defaultTagName: "button", |
| 47 | render, |
| 48 | props: mergeProps<"button">( |
| 49 | { |
| 50 | type: render ? undefined : "button", |
| 51 | className: cn( |
| 52 | "relative inline-flex shrink-0 cursor-pointer items-center whitespace-nowrap border outline-none transition-[box-shadow,scale] [&:not([aria-haspopup]):not(:disabled):not([aria-disabled=true])]:will-change-[scale] [&:active:not([aria-haspopup])]:scale-[0.97] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 disabled:pointer-events-none disabled:opacity-64 aria-disabled:cursor-not-allowed aria-disabled:opacity-64 [&_svg]:shrink-0", |
| 53 | parts[part], |
| 54 | multiline && "h-auto min-h-8 py-0.75 disabled:opacity-100 sm:h-auto", |
| 55 | tone === "muted" && "text-muted-foreground/70 hover:text-foreground/80", |
| 56 | tone === "primary" && "text-primary hover:text-primary", |
| 57 | tone === "destructive" && |
| 58 | "text-destructive hover:text-destructive data-pressed:text-destructive", |
| 59 | className, |
| 60 | ), |
| 61 | }, |
| 62 | props, |
| 63 | { |
| 64 | children: |
| 65 | part === "select" ? ( |
| 66 | <> |
| 67 | {children} |
| 68 | <span data-slot="select-icon"> |
| 69 | <ChevronDownIcon className={THREAD_DETAILS_PANEL_CHEVRON_CLASS} /> |
| 70 | </span> |
| 71 | </> |
| 72 | ) : ( |
| 73 | children |
| 74 | ), |
| 75 | }, |
| 76 | ), |
| 77 | }); |
| 78 | if (!panel) { |
| 79 | return ( |
| 80 | <Button |
| 81 | {...props} |
| 82 | render={render} |
| 83 | size={multiline ? "sm-multiline" : size} |
| 84 | variant={variant} |
| 85 | className={className} |
| 86 | > |
| 87 | {children} |
| 88 | </Button> |
| 89 | ); |
| 90 | } |
| 91 | return control; |
| 92 | } |