| 1 | import type { ComponentProps } from "react"; |
| 2 | |
| 3 | import { cn } from "~/lib/utils"; |
| 4 | |
| 5 | /** One glass backdrop until a top attachment needs the composer to cover its overlap. */ |
| 6 | function Shell({ |
| 7 | contextStrip = false, |
| 8 | className, |
| 9 | ...props |
| 10 | }: ComponentProps<"div"> & { contextStrip?: boolean }) { |
| 11 | return ( |
| 12 | <div |
| 13 | data-slot="composer-shell" |
| 14 | data-with-context={contextStrip || undefined} |
| 15 | className={cn( |
| 16 | "@container/composer-surface group/composer-surface relative isolate mx-auto w-full max-w-(--chat-content-max-width)", |
| 17 | "[--chat-composer-drawer-inset:1.375rem] [--chat-composer-glass-surface:var(--card)] [--chat-composer-outline:rgb(0_0_0/8%)]", |
| 18 | "dark:[--chat-composer-glass-surface:var(--surface-raised)] dark:[--chat-composer-highlight:rgb(255_255_255/3%)] dark:[--chat-composer-outline:color-mix(in_srgb,var(--color-white)_5%,transparent)]", |
| 19 | "[html[data-theme-id]_&]:[--chat-composer-glass-surface:var(--app-theme-surface-raised)] [html[data-theme-id]_&]:[--chat-composer-outline:var(--app-theme-toolbar-border)]", |
| 20 | "dark:[html[data-theme-id]:not([data-theme-id=t3-chat])_&]:[--chat-composer-highlight:color-mix(in_srgb,var(--app-theme-input)_12%,transparent)] dark:[html[data-theme-id]:not([data-theme-id=t3-chat])_&]:[--chat-composer-outline:color-mix(in_srgb,var(--app-theme-input)_30%,var(--background))]", |
| 21 | "dark:[html[data-theme-id=t3-chat]_&]:[--chat-composer-highlight:color-mix(in_srgb,#432d48_12%,transparent)] dark:[html[data-theme-id=t3-chat]_&]:[--chat-composer-outline:#241e28]", |
| 22 | "before:pointer-events-none before:absolute before:inset-0 before:z-0 before:rounded-3xl before:bg-(--chat-composer-glass-surface)/(--glass-opacity) before:backdrop-blur-(--glass-blur) before:backdrop-saturate-(--glass-saturation)", |
| 23 | "not-supports-[((backdrop-filter:blur(1px))_or_(-webkit-backdrop-filter:blur(1px)))]:before:bg-(--chat-composer-glass-surface)", |
| 24 | "has-data-[composer-banner-surface=attached]:before:hidden data-model-strip-transition:before:hidden", |
| 25 | contextStrip && [ |
| 26 | "[--chat-composer-context-extension:2.25rem] sm:[--chat-composer-context-extension:2rem]", |
| 27 | // Keep one continuous backdrop around the fixed-pixel corners and rem-sized strip inset. |
| 28 | "supports-[clip-path:shape(from_0_0,line_to_1px_1px)]:before:rounded-none", |
| 29 | "before:[clip-path:shape(from_0_22px,curve_to_22px_0_with_0_9.85px/9.85px_0,line_to_calc(100%-22px)_0,curve_to_100%_22px_with_calc(100%-9.85px)_0/100%_9.85px,line_to_100%_calc(100%-var(--chat-composer-context-extension)-var(--chat-composer-drawer-inset)),curve_to_calc(100%-var(--chat-composer-drawer-inset))_calc(100%-var(--chat-composer-context-extension))_with_100%_calc(100%-var(--chat-composer-context-extension)-var(--chat-composer-drawer-inset)*0.4477)/calc(100%-var(--chat-composer-drawer-inset)*0.4477)_calc(100%-var(--chat-composer-context-extension)),line_to_calc(100%-var(--chat-composer-drawer-inset))_calc(100%-16px),curve_to_calc(100%-var(--chat-composer-drawer-inset)-16px)_100%_with_calc(100%-var(--chat-composer-drawer-inset))_calc(100%-7.16px)/calc(100%-var(--chat-composer-drawer-inset)-7.16px)_100%,line_to_calc(var(--chat-composer-drawer-inset)+16px)_100%,curve_to_var(--chat-composer-drawer-inset)_calc(100%-16px)_with_calc(var(--chat-composer-drawer-inset)+7.16px)_100%/var(--chat-composer-drawer-inset)_calc(100%-7.16px),line_to_var(--chat-composer-drawer-inset)_calc(100%-var(--chat-composer-context-extension)),curve_to_0_calc(100%-var(--chat-composer-context-extension)-var(--chat-composer-drawer-inset))_with_calc(var(--chat-composer-drawer-inset)*0.4477)_calc(100%-var(--chat-composer-context-extension))/0_calc(100%-var(--chat-composer-context-extension)-var(--chat-composer-drawer-inset)*0.4477),line_to_0_22px,close)]", |
| 30 | "not-supports-[clip-path:shape(from_0_0,line_to_1px_1px)]:before:bottom-(--chat-composer-context-extension)", |
| 31 | ], |
| 32 | className, |
| 33 | )} |
| 34 | {...props} |
| 35 | /> |
| 36 | ); |
| 37 | } |
| 38 | |
| 39 | const outlineClasses = |
| 40 | "after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-(--chat-composer-outline) dark:after:inset-shadow-2xs dark:after:inset-shadow-(color:--chat-composer-highlight)"; |
| 41 | |
| 42 | // The bottom strip continues the outline, so leave the seam between its corners open. |
| 43 | const contextSeamClasses = |
| 44 | "group-data-with-context/composer-surface:after:[clip-path:polygon(0_0,100%_0,100%_100%,calc(100%-22px)_100%,calc(100%-22px)_calc(100%-2px),22px_calc(100%-2px),22px_100%,0_100%)]"; |
| 45 | |
| 46 | function Host({ className, ...props }: ComponentProps<"div">) { |
| 47 | return ( |
| 48 | <div |
| 49 | data-slot="composer-host" |
| 50 | className={cn( |
| 51 | "relative z-10 w-full rounded-3xl shadow-composer after:z-1 dark:shadow-none", |
| 52 | outlineClasses, |
| 53 | contextSeamClasses, |
| 54 | "group-has-data-[composer-banner-surface=attached]/composer-surface:shadow-none group-has-data-[composer-banner-surface=attached]/composer-surface:after:hidden", |
| 55 | className, |
| 56 | )} |
| 57 | {...props} |
| 58 | /> |
| 59 | ); |
| 60 | } |
| 61 | |
| 62 | function Main({ className, ...props }: ComponentProps<"div">) { |
| 63 | return ( |
| 64 | <div |
| 65 | data-chat-composer-main-surface="true" |
| 66 | className={cn( |
| 67 | "group relative z-10 rounded-3xl p-px", |
| 68 | // The host also wraps shoulder tabs, so its backdrop would extend above the input. |
| 69 | "group-data-model-strip-transition/composer-surface:bg-(--chat-composer-glass-surface)/(--glass-opacity) group-data-model-strip-transition/composer-surface:backdrop-blur-(--glass-blur) group-data-model-strip-transition/composer-surface:backdrop-saturate-(--glass-saturation)", |
| 70 | outlineClasses, |
| 71 | contextSeamClasses, |
| 72 | "after:z-20 after:hidden group-has-data-[composer-banner-surface=attached]/composer-surface:after:block group-data-model-strip-transition/composer-surface:after:block", |
| 73 | "group-has-data-[composer-banner-surface=attached]/composer-surface:bg-(--chat-composer-glass-surface)/(--glass-opacity) group-has-data-[composer-banner-surface=attached]/composer-surface:backdrop-blur-(--glass-blur) group-has-data-[composer-banner-surface=attached]/composer-surface:backdrop-saturate-(--glass-saturation)", |
| 74 | "group-has-data-[composer-banner-surface=attached]/composer-surface:shadow-composer dark:group-has-data-[composer-banner-surface=attached]/composer-surface:shadow-none", |
| 75 | "not-supports-[((backdrop-filter:blur(1px))_or_(-webkit-backdrop-filter:blur(1px)))]:group-has-data-[composer-banner-surface=attached]/composer-surface:bg-(--chat-composer-glass-surface)", |
| 76 | "group-has-data-[composer-banner-surface=attached]/composer-surface:**:data-[chat-composer-mobile-collapsed=true]:min-h-[calc(1rem+1px)]", |
| 77 | className, |
| 78 | )} |
| 79 | {...props} |
| 80 | /> |
| 81 | ); |
| 82 | } |
| 83 | |
| 84 | function ContextStrip({ className, ...props }: ComponentProps<"div">) { |
| 85 | return ( |
| 86 | <div |
| 87 | data-slot="composer-context-strip" |
| 88 | className={cn( |
| 89 | "group/composer-context relative isolate mx-auto -mt-4 flex w-[calc(100%-2*var(--chat-composer-drawer-inset))] items-center gap-2 overflow-x-clip overflow-y-visible px-2 pt-5 pb-1", |
| 90 | "before:absolute before:inset-0 before:-z-1 before:rounded-b-2xl before:border before:border-(--chat-composer-outline) before:mask-b-from-transparent before:mask-b-from-4 before:mask-b-to-black before:mask-b-to-4 before:shadow-composer", |
| 91 | "dark:before:border-white/7 dark:before:bg-composer-seam-below dark:before:shadow-composer-dark", |
| 92 | "group-has-data-[composer-banner-surface=attached]/composer-surface:before:bg-(--chat-composer-glass-surface)/(--glass-opacity) group-has-data-[composer-banner-surface=attached]/composer-surface:before:backdrop-blur-(--glass-blur) group-has-data-[composer-banner-surface=attached]/composer-surface:before:backdrop-saturate-(--glass-saturation)", |
| 93 | "not-supports-[clip-path:shape(from_0_0,line_to_1px_1px)]:before:bg-(--chat-composer-glass-surface)/(--glass-opacity) not-supports-[clip-path:shape(from_0_0,line_to_1px_1px)]:before:backdrop-blur-(--glass-blur) not-supports-[clip-path:shape(from_0_0,line_to_1px_1px)]:before:backdrop-saturate-(--glass-saturation)", |
| 94 | className, |
| 95 | )} |
| 96 | {...props} |
| 97 | /> |
| 98 | ); |
| 99 | } |
| 100 | |
| 101 | export const ComposerSurface = { Shell, Host, Main, ContextStrip }; |