import { brand, defaultBrand, defineProperties, outerBorderStops, stops, token } from './tokens.macro' with {type: 'macro'}; import {color, css, style, StyleString} from '@react-spectrum/s2/style' with {type: 'macro'}; import {getEventTarget} from 'react-aria/private/utils/shadowdom/DOMFunctions'; import {Group, GroupProps} from 'react-aria-components/Group'; import {isFocusable} from 'react-aria/private/utils/isFocusable'; import {mergeStyles} from '@react-spectrum/s2/mergeStyles'; import {useState} from 'react'; const STATE_TRANSITION = '700ms cubic-bezier(0.32, 0.72, 0, 1)'; /* The brand color drives the hue of the entire component. Override --brand on the PromptField (or any ancestor) to retheme it — all gradients, shadows, and borders are derived from this hue via OKLCH relative colors in tokens.macro. The default is the fuchsia primary, rgb(236, 105, 255). */ defineProperties(` @property --brand { syntax: ''; initial-value: ${defaultBrand()}; inherits: true; } @property --con-hue-opacity { syntax: ''; initial-value: 0%; inherits: false; } @property --bg-stop-1 { syntax: ''; initial-value: #0000; inherits: false; } @property --bg-stop-2 { syntax: ''; initial-value: #0000; inherits: false; } @property --bg-stop-3 { syntax: ''; initial-value: #0000; inherits: false; } @property --bg-stop-4 { syntax: ''; initial-value: #0000; inherits: false; } `); const containerBackground = css(` transition: --con-hue-opacity ${STATE_TRANSITION}, --bg-stop-1 ${STATE_TRANSITION}, --bg-stop-2 ${STATE_TRANSITION}, --bg-stop-3 ${STATE_TRANSITION}, --bg-stop-4 ${STATE_TRANSITION}, box-shadow ${STATE_TRANSITION}; background: linear-gradient( to bottom, light-dark(rgb(255 255 255 / 75%), rgb(0 0 0 / 40%)) 0% 37%, light-dark(rgb(255 255 255 / 15%), rgb(0 0 0 / 12%)) 83% 100% ), radial-gradient( 50% 50% at -20% 100%, rgb(from ${token('container.gradient.con-hue.generating.stop-3')} r g b / var(--con-hue-opacity)), transparent ), radial-gradient( 70% 60% at 5% 80%, rgb(from ${token('container.gradient.con-hue.generating.stop-2')} r g b / var(--con-hue-opacity)), transparent ), radial-gradient( 70% 50% at 40% 80%, rgb(from ${token('container.gradient.con-hue.generating.stop-1')} r g b / var(--con-hue-opacity)), transparent ), radial-gradient( circle at right bottom, var(--bg-stop-1) 0%, var(--bg-stop-2) 35%, var(--bg-stop-3) 82%, var(--bg-stop-4) 100% ); --border-color: ${token(`container.border.default`)}; --inset-shadow-color: ${color('transparent-white-50')}; --drop-shadow-color: light-dark(${brand(0.5826, 0.2265, -0.4, 0.05)}, ${brand(0.6617, 0.2508, -0.5, 0.05)}); --prominent-outline-glow: ; box-shadow: var(--prominent-outline-glow) inset 0 0 0 1px var(--border-color), inset 0 6px 15px 0 var(--inset-shadow-color), inset 0 0 0 0 transparent, /* placeholder for generating state so transition is smooth */ inset 0 -5px 21.6px 0 ${color('transparent-white-50')}, inset 0 24px 32px 0 ${color('transparent-white-50')}, 0 -3px 10px 1px var(--drop-shadow-color); &[data-variant=prominent] { /* trailing comma is intentional so it can be interpolated above */ --prominent-outline-glow: 0 20px 20px -24px ${token('outline-glow.gradient.generating.stop-3')}, inset 0 -20px 20px -24px ${token('outline-glow.gradient.generating.stop-3')},; &[data-focused] { --prominent-outline-glow: 0 20px 20px -24px transparent, inset 0 -20px 20px -24px transparent,; } } &[data-state=idle] { &[data-variant=balanced] { ${stops('idle', 'default', 'balanced')} &[data-hovered] { ${stops('idle', 'hover', 'balanced')} } } &[data-variant=prominent] { ${stops('idle', 'default', 'prominent')} &[data-hovered] { ${stops('idle', 'hover', 'prominent')} } } &[data-variant=subtle] { --con-hue-opacity: 0%; --bg-stop-1: light-dark(white, ${color('gray-75')}); --bg-stop-2: light-dark(white, ${color('gray-75')}); --bg-stop-3: light-dark(white, ${color('gray-75')}); --bg-stop-4: light-dark(white, ${color('gray-75')}); --border-color: ${token(`container.border.focus`)}; --drop-shadow-color: transparent; &[data-hovered] { ${stops('idle', 'hover', 'subtle')} --border-color: ${token(`container.border.default`)}; --drop-shadow-color: light-dark(${brand(0.5826, 0.2265, -0.4, 0.05)}, ${brand(0.6617, 0.2508, -0.5, 0.05)}); } } &[data-focused][data-focused] { --con-hue-opacity: 0%; --bg-stop-1: light-dark(white, ${color('gray-75')}); --bg-stop-2: light-dark(white, ${color('gray-75')}); --bg-stop-3: light-dark(white, ${color('gray-75')}); --bg-stop-4: light-dark(white, ${color('gray-75')}); --border-color: ${token(`container.border.focus`)}; --inset-shadow-color: transparent; --drop-shadow-color: transparent; } } &[data-state=generating] { box-shadow: var(--prominent-outline-glow) inset 0 0 0 1px var(--border-color), inset 0 6px 15px 0 var(--inset-shadow-color), inset 0 -32px 100px -50px ${token('container.color.inner-shadow.generating')}, inset 0 -5px 21.6px 0 ${color('transparent-white-50')}, inset 0 24px 32px 0 ${color('transparent-white-50')}, 0 -3px 10px 1px var(--drop-shadow-color), 0 6px 83px rgb(from ${token('outer-border.gradient.ob-spread-shadow.generating.stop-3')} r g b / var(--spread-shadow-opacity)); &[data-variant=balanced] { --spread-shadow-opacity: ${token('outer-border.opacity.spread-bg-balanced')}%; ${stops('generating', 'default', 'balanced')} &[data-hovered] { ${stops('generating', 'hover', 'balanced')} } } &[data-variant=prominent] { --spread-shadow-opacity: ${token('outer-border.opacity.spread-bg-prominent')}%; ${stops('generating', 'default', 'prominent')} &[data-hovered] { ${stops('generating', 'hover', 'prominent')} } } &[data-variant=subtle] { --spread-shadow-opacity: ${token('outer-border.opacity.spread-bg-subtle')}%; ${stops('generating', 'default', 'subtle')} &[data-hovered] { ${stops('generating', 'hover', 'subtle')} } } } `); const outerBorder = css(` --outer-drop-shadow-color: ${token('outer-border.color.drop-shadow.ob-border.default')}; padding: 6px; border-radius: calc(24px + 6px); transition: --bg-stop-1 ${STATE_TRANSITION}, --bg-stop-2 ${STATE_TRANSITION}, --bg-stop-3 ${STATE_TRANSITION}, box-shadow ${STATE_TRANSITION}; background: linear-gradient( to right, var(--bg-stop-1) 0%, var(--bg-stop-2) 37%, var(--bg-stop-3) 77% ); box-shadow: 0 2px 4px var(--outer-drop-shadow-color), 0 6px 16px -10px var(--outer-border-hue); &[data-variant=balanced] { ${outerBorderStops('balanced')} } &[data-variant=prominent] { ${outerBorderStops('prominent')} &[data-state=idle][data-focused] { --bg-stop-1: ${token('outer-border.gradient.ob-border.stop-1')}; --bg-stop-2: ${token('outer-border.gradient.ob-border.stop-1')}; --bg-stop-3: ${token('outer-border.gradient.ob-border.stop-1')}; } } &[data-variant=subtle] { --outer-drop-shadow-color: ${token('outer-border.color.drop-shadow.ob-border.subtle')}; --outer-border-hue: transparent; --bg-stop-1: ${token('outer-border.gradient.ob-border.stop-1')}; --bg-stop-2: ${token('outer-border.gradient.ob-border.stop-1')}; --bg-stop-3: ${token('outer-border.gradient.ob-border.stop-1')}; &[data-hovered] { --outer-drop-shadow-color: ${token('outer-border.color.drop-shadow.ob-border.default')}; ${outerBorderStops('subtle')} } } &[data-state=idle][data-focused] { --bg-stop-1: ${token('outer-border.gradient.ob-border.stop-1')}; --bg-stop-2: ${token('outer-border.gradient.ob-border.stop-1')}; --bg-stop-3: ${token('outer-border.gradient.ob-border.stop-1')}; --outer-border-hue: transparent; --outer-drop-shadow-color: ${token('outer-border.color.drop-shadow.ob-border.focus')}; } `); interface PropFieldContainerProps extends Omit { children: React.ReactNode; variant: 'balanced' | 'prominent' | 'subtle'; isGenerating: boolean; isDropTarget: boolean; brandColor?: string; styles?: StyleString; inputRef: React.RefObject; } export function PromptFieldContainer(props: PropFieldContainerProps) { let {variant, isGenerating, isDropTarget, styles, inputRef, brandColor, ...otherProps} = props; let [isFocused, setFocused] = useState(false); return ( { if (e.isTrusted) { setFocused(true); } }} onBlur={e => { if (e.isTrusted) { setFocused(false); } }} onPointerDown={e => { // If not clicking on something focusable within the prompt field, focus the input. let target = getEventTarget(e) as Element | null; while (target && target !== e.currentTarget && !isFocusable(target)) { target = target.parentElement; } if (target === e.currentTarget) { e.preventDefault(); inputRef.current?.focus(); } }}> {({isHovered}) => (
{isDropTarget && (
)} {props.children}
)} ); }