Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create spright-chat-conversation and spright-chat-message components #2529

Open
wants to merge 67 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
8a3e0f7
Component APIs initial pass
jattasNI Nov 25, 2024
53d8a83
Copy rectange and rename ai-chat-text-bubble
jattasNI Nov 25, 2024
dde0e41
Fix build
jattasNI Nov 25, 2024
ff03a9e
Copy rectangle docs for AI Chat text bubble
jattasNI Nov 25, 2024
f318f03
Refactor into single MDX for all AI Chat components
jattasNI Nov 25, 2024
3996846
Start implementing chat bubble
jattasNI Nov 25, 2024
f31ed38
Remove "AI" from names
jattasNI Nov 25, 2024
2ba3b86
Rename bubbles to messages
jattasNI Nov 26, 2024
3f94aa5
Slot message content
jattasNI Nov 26, 2024
2837ba3
Markdown example
jattasNI Nov 26, 2024
9b5866c
positioning based on who sent message
jattasNI Nov 26, 2024
20f73fc
Move message under chat folder
jattasNI Nov 26, 2024
0b15993
Initial conversation component
jattasNI Nov 26, 2024
badb124
Docs for conversation
jattasNI Nov 26, 2024
5aaf44d
Padding between messages
jattasNI Nov 26, 2024
ecddda6
Only outgoing messages in bubbles
jattasNI Nov 27, 2024
d232c8e
background color
jattasNI Nov 27, 2024
df26a48
More example message types
jattasNI Nov 27, 2024
ad64243
Conversation vertical scroll
jattasNI Nov 27, 2024
dd13b59
Add window component (initially copied from conversation)
jattasNI Nov 27, 2024
3649d59
Window toolbars
jattasNI Nov 27, 2024
81b81aa
Chat input toolbar
jattasNI Nov 27, 2024
0eb06b5
Type in chat window
jattasNI Nov 27, 2024
2ac76a7
Merge branch 'main' into sprigiht-ai-components
jattasNI Dec 2, 2024
a67499c
Padding around conversation
jattasNI Dec 2, 2024
08ad339
Work around theme provider conflict in build
jattasNI Dec 2, 2024
e23117e
work around theme provider styles
jattasNI Dec 2, 2024
ffe0add
Merge branch 'main' into spright-chat-components
jattasNI Jan 17, 2025
2207d4f
Fixture errors
jattasNI Jan 21, 2025
463a99f
More Fixture build errors
jattasNI Jan 21, 2025
a57c934
Fix build
jattasNI Jan 21, 2025
cc8a9b2
Productize ChatConversation and ChatMessage elements
joseahdz Jan 30, 2025
5809c06
Delete README.md
joseahdz Jan 30, 2025
1e5b67a
Small tweaks
joseahdz Jan 30, 2025
d05c23e
Merge branch 'main' into spright-chat-components-final
joseahdz Jan 30, 2025
0f6ab82
Update chat.stories.ts
joseahdz Jan 31, 2025
d3a1010
Change files
joseahdz Jan 31, 2025
4b9ca60
Fix lint and beachball errors
joseahdz Jan 31, 2025
96d9bae
Reverted lock.json files
joseahdz Jan 31, 2025
5c8584b
Implemented feedback
joseahdz Jan 31, 2025
48f39dc
Merge branch 'main' into spright-chat-components-final
joseahdz Jan 31, 2025
3160b95
Simpsons reference
jattasNI Feb 3, 2025
5581ea4
Status table
jattasNI Feb 3, 2025
0232ae3
Improve doc content
jattasNI Feb 3, 2025
761c74a
linting
jattasNI Feb 3, 2025
f9558ce
Added chat messages of different sizes
joseahdz Feb 4, 2025
4dfac11
Ran lint tool and added conversation tests
joseahdz Feb 4, 2025
4314626
Fixed typo
joseahdz Feb 4, 2025
a9fce4d
Implemented latest feedback.
joseahdz Feb 5, 2025
02f1468
More feedback
joseahdz Feb 6, 2025
75d40cf
Added margins to chat messages in storybook matrix stories
joseahdz Feb 6, 2025
1d87912
Updating margins
joseahdz Feb 6, 2025
5956cd5
Removed usage of png file and started using svg image
joseahdz Feb 6, 2025
d798feb
Fixed formatting
joseahdz Feb 7, 2025
3cbe591
Implemented Milan's feedback
joseahdz Feb 9, 2025
ab06456
Use nimble icon svg
rajsite Feb 10, 2025
1e91cac
Merge branch 'main' into spright-chat-components-final
joseahdz Feb 10, 2025
7ab321e
Merge branch 'main' into spright-chat-components-final
rajsite Feb 11, 2025
8dd5207
Consistent chat-conversation naming
rajsite Feb 11, 2025
ab15b5b
Enable overflow on message content
rajsite Feb 11, 2025
df6e01a
Make message stories internal
rajsite Feb 11, 2025
0179425
lint
rajsite Feb 11, 2025
de82196
Remove duplicate config for messages
rajsite Feb 11, 2025
f80c5bb
Template consistency
rajsite Feb 12, 2025
19dc127
Refactor sizing stories
rajsite Feb 12, 2025
7d8c7aa
lint
rajsite Feb 12, 2025
9ee640b
Move overflow to inner message div
rajsite Feb 12, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of the changes I pushed:

  • Keeping the storybook file structure and naming aligned with the components. So story naming aligns to chat-conversation
  • Made the storybook templates more consistent (use double quotes on attributes, use Enum references instead of hard coded strings)
  • Followed the pattern of mapping column and made the chat-message stories internal. Users are expected to use chat-conversation with chat-message and chat-message is not standalone, so just document it inside of chat-conversation
  • Removed most of the chat-message standalone matrix stories. We really only care about chat-conversation's containing chat-messages so focusing on those matrix stories.

"type": "minor",
"comment": "Productize ChatConversation and ChatMessage elements",
"packageName": "@ni/spright-components",
"email": "jose.a.hernandez@ni.com",
"dependentChangeType": "patch"
}
2 changes: 2 additions & 0 deletions packages/spright-components/src/all-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@

joseahdz marked this conversation as resolved.
Show resolved Hide resolved
import '@ni/nimble-components/dist/esm/all-components';
joseahdz marked this conversation as resolved.
Show resolved Hide resolved

import './chat/conversation';
import './chat/message';
import './rectangle';
25 changes: 25 additions & 0 deletions packages/spright-components/src/chat/conversation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { DesignSystem, FoundationElement } from '@microsoft/fast-foundation';
import { styles } from './styles';
import { template } from './template';

declare global {
interface HTMLElementTagNameMap {
'spright-chat-conversation': ChatConversation;
}
}

/**
* A Spright component for displaying a series of chat messages
*/
export class ChatConversation extends FoundationElement {}

const sprightChatConversation = ChatConversation.compose({
baseName: 'chat-conversation',
template,
styles
});

DesignSystem.getOrCreate()
.withPrefix('spright')
.register(sprightChatConversation());
export const chatConversationTag = 'spright-chat-conversation';
20 changes: 20 additions & 0 deletions packages/spright-components/src/chat/conversation/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { css } from '@microsoft/fast-element';
import {
applicationBackgroundColor,
mediumPadding,
standardPadding
} from '@ni/nimble-components/dist/esm/theme-provider/design-tokens';
import { display } from '../../utilities/style/display';

export const styles = css`
${display('flex')}

:host {
flex-direction: column;
justify-content: flex-start;
row-gap: ${standardPadding};
padding: ${mediumPadding};
background: ${applicationBackgroundColor};
overflow-y: scroll;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { html } from '@microsoft/fast-element';
import type { ChatConversation } from '.';

/* eslint-disable @typescript-eslint/indent */
// prettier-ignore
export const template = html<ChatConversation>`<slot></slot>`;
/* eslint-enable @typescript-eslint/indent */
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { html } from '@microsoft/fast-element';
import { ChatConversation, chatConversationTag } from '..';
import { fixture, type Fixture } from '../../../utilities/tests/fixture';

async function setup(): Promise<Fixture<ChatConversation>> {
return await fixture<ChatConversation>(
html`<${chatConversationTag}></${chatConversationTag}>`
);
}

describe('ChatConversation', () => {
let element: ChatConversation;
let connect: () => Promise<void>;
let disconnect: () => Promise<void>;

beforeEach(async () => {
({ element, connect, disconnect } = await setup());
});

afterEach(async () => {
await disconnect();
});

it('can construct an element instance', () => {
expect(document.createElement(chatConversationTag)).toBeInstanceOf(
ChatConversation
);
});

it('should have a slot element in the shadow DOM', async () => {
await connect();
expect(element.shadowRoot?.querySelector('SLOT')).not.toBeNull();
});
});
34 changes: 34 additions & 0 deletions packages/spright-components/src/chat/message/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { attr } from '@microsoft/fast-element';
import { DesignSystem, FoundationElement } from '@microsoft/fast-foundation';
import { styles } from './styles';
import { template } from './template';
import { ChatMessageType } from '../types';

declare global {
interface HTMLElementTagNameMap {
'spright-chat-message': ChatMessage;
}
}

/**
* A Spright component for displaying a chat message
*/
export class ChatMessage extends FoundationElement {
/**
* @public
* The message type of this message in the chat conversation
* @remarks
* HTML Attribute: message-type
*/
@attr({ attribute: 'message-type' })
public readonly messageType: ChatMessageType = ChatMessageType.outbound;
}

const sprightChatMessage = ChatMessage.compose({
baseName: 'chat-message',
template,
styles
});

DesignSystem.getOrCreate().withPrefix('spright').register(sprightChatMessage());
export const chatMessageTag = 'spright-chat-message';
50 changes: 50 additions & 0 deletions packages/spright-components/src/chat/message/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { css } from '@microsoft/fast-element';
import {
bodyFont,
bodyFontColor,
borderHoverColor,
borderWidth,
fillSelectedColor,
mediumPadding
} from '@ni/nimble-components/dist/esm/theme-provider/design-tokens';
import { display } from '../../utilities/style/display';

export const styles = css`
${display('flex')}

:host {
min-width: 16px;
min-height: 16px;

flex-direction: row;
justify-content: center;
flex-shrink: 0;
}

:host([message-type='outbound']) {
justify-content: flex-end;
}

:host([message-type='inbound']) {
justify-content: flex-start;
}

div {
max-width: calc(100% - 200px);
width: fit-content;
height: fit-content;
padding: ${mediumPadding};
font: ${bodyFont};
color: ${bodyFontColor};
joseahdz marked this conversation as resolved.
Show resolved Hide resolved
}

:host([message-type='outbound']) div {
joseahdz marked this conversation as resolved.
Show resolved Hide resolved
background: ${fillSelectedColor};
border: ${borderWidth} solid ${borderHoverColor};
border-radius: 8px 8px 0px 8px;
}

:host([message-type='inbound']) div {
border-radius: 8px 8px 8px 0px;
}
`;
11 changes: 11 additions & 0 deletions packages/spright-components/src/chat/message/template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { html } from '@microsoft/fast-element';
import type { ChatMessage } from '.';

/* eslint-disable @typescript-eslint/indent */
// prettier-ignore
export const template = html<ChatMessage>`
<div>
<slot></slot>
</div>
`;
joseahdz marked this conversation as resolved.
Show resolved Hide resolved
/* eslint-enable @typescript-eslint/indent */
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { html } from '@microsoft/fast-element';
import { ChatMessage, chatMessageTag } from '..';
import { fixture, type Fixture } from '../../../utilities/tests/fixture';
import { ChatMessageType } from '../../types';

async function setup(): Promise<Fixture<ChatMessage>> {
return await fixture<ChatMessage>(
html`<${chatMessageTag}>Some message</${chatMessageTag}>`
);
}

describe('ChatMessage', () => {
let element: ChatMessage;
let connect: () => Promise<void>;
let disconnect: () => Promise<void>;

beforeEach(async () => {
({ element, connect, disconnect } = await setup());
});

afterEach(async () => {
await disconnect();
});

it('can construct an element instance', () => {
expect(document.createElement(chatMessageTag)).toBeInstanceOf(
ChatMessage
);
});

it('should have a slot element in the shadow DOM', async () => {
joseahdz marked this conversation as resolved.
Show resolved Hide resolved
await connect();
expect(element.shadowRoot?.querySelector('SLOT')).not.toBeNull();
expect(
element?.innerText?.includes('Some message', undefined)
).toBeTrue();
});

it("should initialize 'message-type' to default", async () => {
await connect();
expect(element.messageType).toBe(ChatMessageType.outbound);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ChatMessageType } from '../../types';

describe('ChatMessage message type', () => {
it('ChatMessageType fails compile if assigning arbitrary string values', () => {
// @ts-expect-error This expect will fail if the enum-like type is missing "as const"
const messageType: ChatMessageType = 'hello';
expect(messageType).toEqual('hello');
});
});
12 changes: 12 additions & 0 deletions packages/spright-components/src/chat/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* A message type in a chat conversation.
* @public
*/
export const ChatMessageType = {
joseahdz marked this conversation as resolved.
Show resolved Hide resolved
outbound: 'outbound',
inbound: 'inbound',
system: 'system'
joseahdz marked this conversation as resolved.
Show resolved Hide resolved
} as const;

export type ChatMessageType =
(typeof ChatMessageType)[keyof typeof ChatMessageType];
6 changes: 5 additions & 1 deletion packages/storybook/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ export function webpackFinal(config) {
];
return config;
}
export const staticDirs = ['public'];
export const staticDirs = [
joseahdz marked this conversation as resolved.
Show resolved Hide resolved
'public',
'../src/nimble',
'../src/spright'
];
export const framework = {
name: '@storybook/html-webpack5'
};
12 changes: 12 additions & 0 deletions packages/storybook/src/docs/component-status.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { tableColumnMappingTag } from '../../../nimble-components/src/table-colu
import { mappingIconTag } from '../../../nimble-components/src/mapping/icon';
import { iconCheckTag } from '../../../nimble-components/src/icons/check';
import { iconTriangleTag } from '../../../nimble-components/src/icons/triangle';
import { iconTriangleFilledTag } from '../../../nimble-components/src/icons/triangle-filled';
import { iconXmarkTag } from '../../../nimble-components/src/icons/xmark';
import { tableFitRowsHeight } from '../../../nimble-components/src/theme-provider/design-tokens';
import { ComponentFrameworkStatus } from './types';
Expand Down Expand Up @@ -136,6 +137,16 @@ const components = [
angularStatus: ComponentFrameworkStatus.ready,
blazorStatus: ComponentFrameworkStatus.ready
},
{
componentName: 'Chat',
componentHref: './?path=/docs/spright-chat--docs',
designHref:
'https://www.figma.com/design/PO9mFOu5BCl8aJvFchEeuN/Nimble_Components?node-id=12342-81782&node-type=canvas&t=L5GvLaC3injrqWrR-0',
designLabel: 'Figma',
componentStatus: ComponentFrameworkStatus.spright,
angularStatus: ComponentFrameworkStatus.doesNotExist,
blazorStatus: ComponentFrameworkStatus.doesNotExist
},
{
componentName: 'Checkbox',
componentHref: './?path=/docs/components-checkbox--docs',
Expand Down Expand Up @@ -547,6 +558,7 @@ const components = [

const iconMappings = html`
<${mappingIconTag} key="${ComponentFrameworkStatus.ready}" text="Ready" icon="${iconCheckTag}" severity="success" text-hidden></${mappingIconTag}>
<${mappingIconTag} key="${ComponentFrameworkStatus.spright}" text="Spright" icon="${iconTriangleFilledTag}" severity="success"></${mappingIconTag}>
<${mappingIconTag} key="${ComponentFrameworkStatus.incubating}" text="Incubating" icon="${iconTriangleTag}" severity="warning"></${mappingIconTag}>
<${mappingIconTag} key="${ComponentFrameworkStatus.doesNotExist}" text="Does not exist" icon="${iconXmarkTag}" severity="error"></${mappingIconTag}>
`;
Expand Down
1 change: 1 addition & 0 deletions packages/storybook/src/docs/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const ComponentFrameworkStatus = {
ready: 'ready',
spright: 'spright',
incubating: 'incubating',
doesNotExist: 'does_not_exist'
} as const;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { ChatConversation } from '../../../../spright-components/src/chat/conversation';
import { wrap } from '../../utilities/react-wrapper';

export const SprightChatConversation = wrap(ChatConversation);
Loading