Skip to content

Commit 294d5df

Browse files
committed
chore: Adds internal style api v2 to prompt input
1 parent 92ef62f commit 294d5df

7 files changed

Lines changed: 535 additions & 18 deletions

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React, { useEffect, useRef, useState } from 'react';
4+
import clsx from 'clsx';
5+
6+
import { PromptInput, PromptInputProps, SpaceBetween } from '~components';
7+
8+
import { SimplePage } from '../app/templates';
9+
10+
import styles from './style-api-v2.scss';
11+
12+
const menus: PromptInputProps.MenuDefinition[] = [
13+
{
14+
id: 'mentions',
15+
trigger: '@',
16+
filteringType: 'auto',
17+
options: [
18+
{ value: 'user-1', label: 'Alice' },
19+
{ value: 'user-2', label: 'Bob' },
20+
{ value: 'user-3', label: 'Charlie', disabled: true },
21+
],
22+
},
23+
];
24+
25+
const referenceTokens: PromptInputProps.InputToken[] = [
26+
{ type: 'text', value: 'Ask ' },
27+
{ type: 'reference', id: 'ref-1', label: 'Alice', value: 'user-1', menuId: 'mentions' },
28+
{ type: 'text', value: ' about the release' },
29+
];
30+
31+
// Reference token ids become element ids, so the second instance on the page needs its own.
32+
const disabledReferenceTokens: PromptInputProps.InputToken[] = [
33+
{ type: 'text', value: 'Ask ' },
34+
{ type: 'reference', id: 'ref-2', label: 'Alice', value: 'user-1', menuId: 'mentions' },
35+
{ type: 'text', value: ' about the release' },
36+
];
37+
38+
// Owned states (invalid, warning, disabled, read-only) are passed as a different class; the interaction states
39+
// (:hover, :focus-within on the root, :hover, :focus on the text element) are handled by selectors in the stylesheet.
40+
function getStyleClassNames({ invalid, warning, disabled, readOnly }: PromptInputProps) {
41+
const state = invalid ? 'invalid' : warning ? 'warning' : disabled ? 'disabled' : readOnly ? 'readonly' : null;
42+
return {
43+
root: state ? styles[`root-${state}`] : styles.root,
44+
textarea: clsx({
45+
[styles['textarea-disabled']]: disabled,
46+
[styles['textarea-readonly']]: readOnly && !disabled,
47+
[styles.textarea]: !disabled && !readOnly,
48+
}),
49+
actionButton: styles['action-button'],
50+
token: disabled ? styles['token-disabled'] : styles.token,
51+
menu: styles.menu,
52+
};
53+
}
54+
55+
function StyledPromptInput({ value: initialValue = '', ...rest }: Omit<PromptInputProps, 'tokens' | 'menus'>) {
56+
const [value, setValue] = useState(initialValue);
57+
return (
58+
<PromptInput
59+
actionButtonAriaLabel="Send"
60+
{...rest}
61+
value={value}
62+
onChange={({ detail }) => setValue(detail.value)}
63+
{...{ styleClassNames: getStyleClassNames(rest) }}
64+
/>
65+
);
66+
}
67+
68+
function StyledTokenPromptInput({
69+
tokens: initialTokens = [],
70+
openMenu = false,
71+
...rest
72+
}: Omit<PromptInputProps, 'value' | 'menus'> & { openMenu?: boolean }) {
73+
const [tokens, setTokens] = useState<readonly PromptInputProps.InputToken[]>(initialTokens);
74+
const ref = useRef<PromptInputProps.Ref>(null);
75+
// Opens the menu on mount by typing the trigger character, so the menu slot is visible.
76+
useEffect(() => {
77+
if (openMenu) {
78+
ref.current?.focus();
79+
ref.current?.insertText('@', 0, 1);
80+
}
81+
}, [openMenu]);
82+
return (
83+
<PromptInput
84+
ref={ref}
85+
actionButtonAriaLabel="Send"
86+
{...rest}
87+
menus={menus}
88+
tokens={tokens}
89+
onChange={({ detail }) => setTokens(detail.tokens ?? [])}
90+
{...{ styleClassNames: getStyleClassNames(rest) }}
91+
/>
92+
);
93+
}
94+
95+
export default function () {
96+
return (
97+
<SimplePage title="Prompt input - Style API v2" screenshotArea={{ disableAnimations: true }} i18n={{}}>
98+
<SpaceBetween size="m" direction="horizontal">
99+
<StyledPromptInput ariaLabel="Default prompt" value="Summarize the release notes" />
100+
<StyledPromptInput ariaLabel="Prompt with placeholder" placeholder="Ask a question" />
101+
<StyledPromptInput
102+
ariaLabel="Prompt with action button"
103+
value="Summarize the release notes"
104+
actionButtonIconName="send"
105+
/>
106+
</SpaceBetween>
107+
<SpaceBetween size="m" direction="horizontal">
108+
<StyledPromptInput ariaLabel="Invalid prompt" value="Too short" invalid={true} actionButtonIconName="send" />
109+
<StyledPromptInput
110+
ariaLabel="Warning prompt"
111+
value="Almost at the limit"
112+
warning={true}
113+
actionButtonIconName="send"
114+
/>
115+
<StyledPromptInput
116+
ariaLabel="Read-only prompt"
117+
value="Read-only text"
118+
readOnly={true}
119+
actionButtonIconName="send"
120+
/>
121+
</SpaceBetween>
122+
<SpaceBetween size="m" direction="horizontal">
123+
<StyledPromptInput
124+
ariaLabel="Disabled prompt"
125+
value="Cannot be edited"
126+
disabled={true}
127+
actionButtonIconName="send"
128+
/>
129+
<StyledPromptInput ariaLabel="Disabled prompt with placeholder" placeholder="Ask a question" disabled={true} />
130+
</SpaceBetween>
131+
<SpaceBetween size="m" direction="horizontal">
132+
<StyledTokenPromptInput
133+
ariaLabel="Prompt with references"
134+
tokens={referenceTokens}
135+
actionButtonIconName="send"
136+
/>
137+
<StyledTokenPromptInput
138+
ariaLabel="Disabled prompt with references"
139+
tokens={disabledReferenceTokens}
140+
disabled={true}
141+
/>
142+
<StyledTokenPromptInput ariaLabel="Prompt with references and placeholder" placeholder="Ask a question" />
143+
<StyledTokenPromptInput
144+
ariaLabel="Disabled prompt with references and placeholder"
145+
placeholder="Ask a question"
146+
disabled={true}
147+
/>
148+
<StyledTokenPromptInput ariaLabel="Prompt with open menu" openMenu={true} />
149+
</SpaceBetween>
150+
</SimplePage>
151+
);
152+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
@use '~components/styling/prompt-input';
7+
@use '~components/styling/button';
8+
@use '~components/styling/token';
9+
@use '~components/styling/dropdown';
10+
@use '../style-api/palette' as palette;
11+
12+
// Field box. One class per owned state (invalid, warning, disabled, read-only); `:hover` and `:focus-within`
13+
// for the interaction states.
14+
@mixin field {
15+
@include prompt-input.root(
16+
$background: palette.$surface,
17+
$border-color: palette.$border,
18+
$border-width: 2px,
19+
$border-radius: 12px,
20+
$box-shadow: 0 2px 4px palette.$subtle
21+
);
22+
}
23+
24+
.root {
25+
@include field;
26+
27+
&:hover {
28+
@include prompt-input.root($background: palette.$surface-alt, $border-color: palette.$border-accent);
29+
}
30+
31+
&:focus-within {
32+
@include prompt-input.root($border-color: palette.$accent, $box-shadow: 0 0 0 3px palette.$accent-active);
33+
}
34+
}
35+
36+
.root-invalid {
37+
@include field;
38+
@include prompt-input.root($background: palette.$error-surface, $border-color: palette.$error);
39+
40+
&:focus-within {
41+
@include prompt-input.root($box-shadow: 0 0 0 3px palette.$error-hover);
42+
}
43+
}
44+
45+
.root-warning {
46+
@include field;
47+
@include prompt-input.root($background: palette.$warning-surface, $border-color: palette.$warning);
48+
49+
&:focus-within {
50+
@include prompt-input.root($box-shadow: 0 0 0 3px palette.$warning);
51+
}
52+
}
53+
54+
.root-disabled {
55+
@include field;
56+
@include prompt-input.root(
57+
$background: palette.$control-bg-disabled,
58+
$border-color: palette.$border-disabled,
59+
$box-shadow: none
60+
);
61+
}
62+
63+
.root-readonly {
64+
@include field;
65+
@include prompt-input.root($background: palette.$surface-alt);
66+
67+
&:focus-within {
68+
@include prompt-input.root($border-color: palette.$accent, $box-shadow: 0 0 0 3px palette.$accent-active);
69+
}
70+
}
71+
72+
// Text element (native textarea or editable element). `:hover` and `:focus` for the interaction states, a class
73+
// for disabled and read-only.
74+
@mixin text {
75+
@include prompt-input.textarea(
76+
$color: palette.$text,
77+
$padding-block: 10px,
78+
$padding-inline: 16px,
79+
$font-family: monospace,
80+
$font-size: 15px,
81+
$font-weight: 600,
82+
$placeholder-color: palette.$accent,
83+
$placeholder-font-size: 13px,
84+
$placeholder-font-style: normal,
85+
$placeholder-font-weight: 400
86+
);
87+
}
88+
89+
.textarea {
90+
@include text;
91+
92+
&:hover {
93+
@include prompt-input.textarea($color: palette.$accent-hover);
94+
}
95+
96+
&:focus {
97+
@include prompt-input.textarea($color: palette.$accent);
98+
}
99+
}
100+
101+
.textarea-disabled {
102+
@include text;
103+
@include prompt-input.textarea($color: palette.$text-disabled, $placeholder-color: palette.$text-disabled);
104+
}
105+
106+
.textarea-readonly {
107+
@include text;
108+
@include prompt-input.textarea($color: palette.$text, $font-weight: 400);
109+
}
110+
111+
// Action button forwards to Button.root.
112+
.action-button {
113+
@include button.root($color: palette.$accent, $focus-ring-color: palette.$accent);
114+
115+
&:hover {
116+
@include button.root($color: palette.$accent-hover);
117+
}
118+
}
119+
120+
// Reference tokens forward to Token.root.
121+
.token {
122+
@include token.root(
123+
$background: palette.$genai-surface,
124+
$color: palette.$genai-text,
125+
$border-color: palette.$genai,
126+
$border-radius: 6px
127+
);
128+
}
129+
130+
.token-disabled {
131+
@include token.root(
132+
$background: palette.$control-bg-disabled,
133+
$color: palette.$text-disabled,
134+
$border-color: palette.$border-disabled,
135+
$border-radius: 6px
136+
);
137+
}
138+
139+
// Menu forwards to Dropdown.container.
140+
.menu {
141+
@include dropdown.container(
142+
$background: palette.$surface-alt,
143+
$border-radius: 12px,
144+
$box-shadow: 0 4px 12px palette.$subtle-active,
145+
$outline-color: palette.$accent,
146+
$outline-width: 2px
147+
);
148+
}

0 commit comments

Comments
 (0)