Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions apps/storybook/src/patterns/DapLayoutsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
PromptEditor,
RadioGroup,
RadioGroupItem,
ReferenceChip,
RequiredIndicator,
Search,
Select,
Expand Down Expand Up @@ -1043,10 +1044,7 @@ function GmailConnectionPickerExample() {
variant="ghost"
>
{selectedConnection ? (
<span className="flex min-w-0 items-center gap-2 rounded bg-primary/15 px-2 py-1">
<GmailIcon />
<span className="truncate text-sm">{selectedConnection}</span>
</span>
<ReferenceChip label={selectedConnection} icon={<GmailIcon />} />
) : (
<span className="text-sm text-muted-foreground">Select a Gmail connection</span>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
type QuickFormFieldMode,
RadioGroup,
RadioGroupItem,
ReferenceChip,
RequiredIndicator,
ResizableHandle,
ResizablePanel,
Expand Down Expand Up @@ -84,6 +85,7 @@ import {
Redo2,
SlidersHorizontal,
Sparkles,
SquareFunction,
StickyNote,
Trash2,
Underline,
Expand Down Expand Up @@ -1326,10 +1328,7 @@ function SendEmailForm({ spacious = false }: { spacious?: boolean }) {
</button>
</div>
<div className="flex min-h-9 items-center rounded-lg border border-border-subtle bg-surface-overlay px-2.5 text-xs">
<Mail className="mr-1.5 size-4 text-brand" />
<span className="truncate rounded bg-brand-subtle px-1.5 py-0.5">
anurag.krishna@uipath.com
</span>
<ReferenceChip label="anurag.krishna@uipath.com" icon={<Mail />} />
</div>
</div>
<BooleanField label="Save as draft" />
Expand Down Expand Up @@ -1363,9 +1362,10 @@ function SendEmailForm({ spacious = false }: { spacious?: boolean }) {
))}
</div>
<div className="min-h-16 p-2.5">
<span className="rounded-md bg-brand-subtle px-2 py-1 font-mono text-xs text-foreground-accent">
ƒx $vars.recordUpdated1.output.Description
</span>
<ReferenceChip
label="$vars.recordUpdated1.output.Description"
icon={<SquareFunction />}
/>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@ import { type NodeViewProps, NodeViewWrapper } from '@tiptap/react';
import token, { Typography } from '@uipath/apollo-core';
import { useChatState } from '@uipath/apollo-react/chat/headless/providers/chat-state-provider';
import { CHAT_RESOURCE_CHIP_MAX_WIDTH } from '@uipath/apollo-react/chat/service';
import { getReferenceChipColors } from '@uipath/apollo-wind/components/ui/reference-chip-colors';
import React from 'react';
import { fontByVariant } from '../../../utils/font-by-variant';
import { AutopilotChatActionButton } from '../../common/action-button';
import { AutopilotChatTooltip } from '../../common/tooltip';

// Shared with apollo-wind's ReferenceChip so every reference chip uses the same token pair.
const chipColors = getReferenceChipColors().valid;

const ChipContent = styled('span')<{ readonly?: boolean }>(({ readonly }) => ({
display: 'inline-flex',
alignItems: 'center',
padding: `0 ${token.Padding.PadS}`,
backgroundColor: 'var(--color-info-background)',
color: 'var(--color-info-text)',
backgroundColor: chipColors.background,
color: chipColors.text,
borderRadius: `calc(${token.Border.BorderRadiusL} * 2)`,
verticalAlign: 'middle',
...(!readonly && {
Expand All @@ -27,6 +31,7 @@ const ChipContent = styled('span')<{ readonly?: boolean }>(({ readonly }) => ({

const ChipIcon = styled('span')(() => ({
fontFamily: '"Material Icons Outlined"',
color: chipColors.icon,
fontSize: token.Icon.IconXs,
flexShrink: 0,
userSelect: 'none',
Expand All @@ -49,7 +54,7 @@ const ChipDeleteContainer = styled('span')<{ visible: boolean; compactMode: bool
transform: 'translateY(-50%)',
display: 'flex',
alignItems: 'center',
backgroundColor: 'var(--color-info-background)',
backgroundColor: chipColors.background,
borderRadius: `calc(${token.Border.BorderRadiusL} * 2)`,
transition: 'opacity 0.15s ease-in-out',

Expand Down
25 changes: 25 additions & 0 deletions packages/apollo-wind/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,28 @@ MIT - see [LICENSE](./LICENSE)
**Peer Dependencies:** React ≥18.0.0

Built with ❤️ by UiPath | Inspired by [shadcn/ui](https://ui.shadcn.com)

## Reference Chip

`ReferenceChip` displays selected variables, resources and connections with paired
semantic background, text and icon colors. Find its API and state examples in
Storybook under **Apollo Wind → Components → Data Display → Reference Chip**.
The **Patterns → Code Editors → Editor Variables** example uses it through
`PromptEditor`; existing editor consumers need no API changes.

```tsx
import { ReferenceChip } from '@uipath/apollo-wind';

<ReferenceChip
label="alex@example.com"
icon={<ConnectionIcon />}
onRemove={clearConnection}
removeLabel="Remove Outlook connection"
/>
```

The host supplies the icon, selection behavior and localized removal label.
`readOnly` hides removal, `disabled` disables removal, `invalid` uses the paired
error palette, and `selected` adds a visual outline. Keep removable chips outside
other buttons. Inside a picker trigger, omit `onRemove` and use a separate clear
button. Product connection fields must adopt this primitive to receive its styles.
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,7 @@ function EditorVariablesUsagePage() {
ariaLabel="Prompt"
/>
<p className="mt-3 text-xs leading-5 text-muted-foreground">
Try both: choose{' '}
PromptEditor uses the shared ReferenceChip for inserted variables. Try both: choose{' '}
<span className="font-mono text-foreground">Insert → $vars → firstName</span> above, or
type <span className="font-mono text-foreground">$</span> in the editor and pick from the
menu that appears.
Expand Down
6 changes: 4 additions & 2 deletions packages/apollo-wind/src/components/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export * from './message-scroller';
export * from './model-picker';
export * from './multi-select';
export * from './pagination';
export * from './panel-tabs';
export * from './popover';
export * from './portal-container';
export * from './progress';
Expand All @@ -62,13 +63,15 @@ export * from './prompt-suggestions';
export * from './prompt-value-control';
export * from './quick-form-field';
export * from './radio-group';
export { ReferenceChip, type ReferenceChipProps } from './reference-chip';
export { getReferenceChipColors } from './reference-chip-colors';
export * from './resizable';
export * from './scroll-area';
export * from './search';
export * from './select';
export * from './separator';
export * from './sidebar';
export * from './sheet';
export * from './sidebar';
export * from './skeleton';
export * from './slider';
export * from './sonner';
Expand All @@ -78,7 +81,6 @@ export * from './stats-card';
export * from './stepper';
export * from './switch';
export * from './table';
export * from './panel-tabs';
export * from './tabs';
export * from './textarea';
export * from './thinking-indicator';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import DOMPurify from 'dompurify';
import { Marked } from 'marked';
import { useMemo } from 'react';
import { getReferenceChipColors } from '../../reference-chip-colors';
import { usePromptEditorConfig } from '../prompt-editor-config';
import { type PromptEditorToken } from '../types';
import { buildTokenIconSvgMarkup } from './token-icon-markup';
Expand All @@ -13,6 +14,7 @@ const marked = new Marked({ async: false, gfm: true, breaks: true });
* do), and without a per-component JS `.css` import (which doesn't resolve in the bundleless build).
* Scoped under `.prompt-editor-preview`; colors use design tokens.
*/
const referenceColors = getReferenceChipColors().valid;
const MARKDOWN_PREVIEW_STYLES = `
.prompt-editor-preview { color: var(--color-foreground); }
.prompt-editor-preview-empty { color: var(--color-muted-foreground); }
Expand All @@ -39,8 +41,8 @@ const MARKDOWN_PREVIEW_STYLES = `
.prompt-editor-preview th { font-weight: 600; background-color: var(--color-muted); }
.prompt-editor-preview strong { font-weight: 700; }
.prompt-editor-preview em { font-style: italic; }
.prompt-editor-preview .token-pill { display: inline-flex; align-items: center; gap: 3px; height: 20px; padding: 0 4px; border-radius: 4px; font-size: 13px; line-height: 20px; vertical-align: middle; background: var(--color-info-background); color: var(--color-info-text); }
.prompt-editor-preview .token-pill svg { display: block; flex-shrink: 0; color: var(--color-info-icon); width: 14px; height: 14px; }
.prompt-editor-preview .token-pill { display: inline-flex; align-items: center; gap: 3px; height: 20px; padding: 0 4px; border-radius: 4px; font-size: 13px; line-height: 20px; vertical-align: middle; background: ${referenceColors.background}; color: ${referenceColors.text}; }
.prompt-editor-preview .token-pill svg { display: block; flex-shrink: 0; color: ${referenceColors.icon}; width: 14px; height: 14px; }
`;

/** Host override for a variable pill's preview rendering (icon markup and/or label). */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Database, Paperclip, SquareFunction, Variable } from 'lucide-react';
import { memo } from 'react';
import { ReferenceChip } from '../../reference-chip';
import { usePromptEditorConfig } from '../prompt-editor-config';
import type { PromptEditorDiffType, PromptEditorTokenType } from '../types';
import { getPromptEditorTokenColors } from '../types';

/** Leading icon per token type — mirrors the SVG markup `MarkdownPreview` inlines for preview mode. */
const TOKEN_TYPE_ICON: Record<
Expand Down Expand Up @@ -41,83 +41,27 @@ const TokenPillBase = ({
onMouseDown,
}: TokenPillProps) => {
const { strings } = usePromptEditorConfig();
const PROMPT_EDITOR_TOKEN_COLORS = getPromptEditorTokenColors();
const colors =
isInvalid && !diffType ? PROMPT_EDITOR_TOKEN_COLORS.invalid : PROMPT_EDITOR_TOKEN_COLORS.valid;
const Icon = TOKEN_TYPE_ICON[tokenType];

const handleRemoveMouseDown = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
};

const handleRemoveClick = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
onRemove?.();
};

const bgColor = diffType
? diffType === 'added'
? 'rgba(76, 175, 80, 0.15)'
: 'rgba(244, 67, 54, 0.15)'
: colors.background;

const outerClassName = [
'relative inline-flex items-center align-middle gap-[3px] h-5 rounded px-1 text-[13px] leading-5 outline-offset-1',
readonly ? 'cursor-default' : 'cursor-pointer',
diffType === 'removed' ? 'line-through opacity-60' : '',
isSelected ? 'z-10' : '',
]
.filter(Boolean)
.join(' ');

return (
// biome-ignore lint/a11y/noStaticElementInteractions: the pill is a Lexical decorator chip; mousedown sets node selection. Keyboard selection is handled by Lexical at the editor root, and the chip exposes a focusable Remove button.
<span
<ReferenceChip
label={value}
icon={<Icon />}
onRemove={onRemove}
removeLabel={strings.removeToken}
readOnly={readonly}
invalid={isInvalid && !diffType}
selected={isSelected}
onMouseDown={onMouseDown}
className={outerClassName}
style={{
backgroundColor: bgColor,
outline: isSelected ? `2px solid ${colors.border}` : 'none',
}}
>
<span className="inline-flex shrink-0" style={{ color: colors.icon }}>
<Icon className="h-3.5 w-3.5" />
</span>
<span
className="font-normal whitespace-nowrap"
style={{ color: colors.text, fontFamily: "'Noto Sans', sans-serif" }}
>
{value}
</span>
{!readonly && (
<button
type="button"
aria-label={strings.removeToken}
className="inline-flex items-center justify-center p-px border-0 bg-transparent cursor-pointer rounded-[2px] w-4 h-4"
style={{ color: colors.text }}
onMouseDown={handleRemoveMouseDown}
onClick={handleRemoveClick}
>
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18 6L6 18M6 6L18 18"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
)}
</span>
className={`${readonly ? 'cursor-default' : 'cursor-pointer'} ${diffType === 'removed' ? 'line-through opacity-60' : ''}`}
style={
diffType
? {
backgroundColor:
diffType === 'added' ? 'rgba(76, 175, 80, 0.15)' : 'rgba(244, 67, 54, 0.15)',
}
: undefined
}
/>
);
};

Expand Down
20 changes: 3 additions & 17 deletions packages/apollo-wind/src/components/ui/prompt-editor/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getReferenceChipColors } from '../reference-chip-colors';

export type PromptEditorTokenType = 'input' | 'output' | 'state' | 'resource' | 'text';

interface BaseToken {
Expand Down Expand Up @@ -89,23 +91,7 @@ export interface PromptEditorTokenColorConfig {
* Two-state palette: invalid = red, valid = blue. Backed by apollo-wind's semantic design tokens,
* which already resolve per light/dark theme via CSS — no JS theme detection needed.
*/
export const getPromptEditorTokenColors = (): Record<
'valid' | 'invalid',
PromptEditorTokenColorConfig
> => ({
valid: {
background: 'var(--color-info-background)',
border: 'var(--color-info-icon)',
text: 'var(--color-info-text)',
icon: 'var(--color-info-icon)',
},
invalid: {
background: 'var(--color-error-background)',
border: 'var(--color-error-icon)',
text: 'var(--color-error-text)',
icon: 'var(--color-error-icon)',
},
});
export const getPromptEditorTokenColors = getReferenceChipColors;

/** The token-type label subset of `PromptEditorStrings` (structural, to avoid a config↔types cycle). */
export interface PromptEditorTokenTypeLabels {
Expand Down
15 changes: 15 additions & 0 deletions packages/apollo-wind/src/components/ui/reference-chip-colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** Paired reference colors. Keep labels, icons and backgrounds on the same semantic palette. */
export const getReferenceChipColors = () => ({
valid: {
background: 'var(--color-info-background)',
border: 'var(--color-info-icon)',
text: 'var(--color-info-text)',
icon: 'var(--color-info-icon)',
},
invalid: {
background: 'var(--color-error-background)',
border: 'var(--color-error-icon)',
text: 'var(--color-error-text)',
icon: 'var(--color-error-icon)',
},
});
Loading
Loading