Skip to content

Commit

Permalink
Rewriting the UI components rendering layer to support custom components
Browse files Browse the repository at this point in the history
  • Loading branch information
salmenus committed Mar 27, 2024
1 parent 85d589a commit b493dba
Show file tree
Hide file tree
Showing 134 changed files with 5,489 additions and 339 deletions.
8 changes: 8 additions & 0 deletions packages/css/themes/src/naked/components/AiChat.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@import './animation.css';
@import './colors.css';
@import './ChatPicture.css';
@import './ConversationItem.css';
@import './Loader.css';
@import './Message.css';
@import './PromptBox.css';
@import './WelcomeMessage.css';
40 changes: 40 additions & 0 deletions packages/css/themes/src/naked/components/ChatPicture.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.nlux_comp_cht_pic {
position: relative;
display: inline-block;
overflow: hidden;
align-items: stretch;
justify-content: stretch;
width: 50px;
color: var(--foreground-color);
border: 1px solid var(--border-color);
border-radius: 20%;
background-color: var(--background-color);
box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.1);
aspect-ratio: 1;

> .cht_pic_ctn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
aspect-ratio: 1;

> .cht_pic_ltr {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}

> .cht_pic_img {
position: absolute;
top: 0;
left: 0;
display: block;
width: 100%;
height: 100%;
background-size: cover;
}
}
}
17 changes: 17 additions & 0 deletions packages/css/themes/src/naked/components/ConversationItem.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.nlux_comp_cnv_itm {
display: flex;
flex-direction: row;
gap: 0.5em;

> .nlux_comp_msg {
flex: 1;
}
}

.nlux_comp_cnv_itm.nlux_cnv_itm_incoming {
flex-direction: row-reverse;
}

.nlux_comp_cnv_itm.nlux_cnv_itm_outgoing {
flex-direction: row;
}
38 changes: 38 additions & 0 deletions packages/css/themes/src/naked/components/Loader.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.nlux_msg_ldr {
display: flex;
align-items: center;
justify-content: center;

> .spn_ldr_ctn {
width: 17px;

> .spn_ldr {
display: inline-block;
width: 15px;
height: 15px;

transform: rotateZ(45deg);
border-radius: 50%;
perspective: 1000px;

&:before,
&:after {
position: absolute;
top: 0;
left: 0;
display: block;
width: inherit;
height: inherit;
content: '';
transform: rotateX(70deg);
animation: 1s nlux-loader-spin linear infinite;
border-radius: 50%;
}

&:after {
transform: rotateY(70deg);
animation-delay: .4s;
}
}
}
}
22 changes: 22 additions & 0 deletions packages/css/themes/src/naked/components/Message.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@import './colors.css';

.nlux_comp_msg {
display: flex;
align-items: center;
justify-content: flex-start;
min-height: 1.5em;
margin: 0;
padding: 0.5em;
text-align: left;
color: var(--foreground-color);
border: 1px solid var(--border-color);
border-radius: 0.25em;
background-color: var(--background-color);
box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.1);
}

.nlux_comp_msg.nlux_msg_loading {
display: flex;
flex: 0;
justify-content: center;
}
63 changes: 63 additions & 0 deletions packages/css/themes/src/naked/components/PromptBox.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
.nlux_comp_prmpt_box {
display: flex;
align-items: stretch;
flex-direction: row;
justify-content: center;
padding: 0.5em;

color: var(--foreground-color);
border: 1px solid var(--border-color);
border-radius: 0.25em;
background-color: var(--background-color);
box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.1);
gap: 0.5em;

> textarea {
flex: 1;
resize: none;
border: none;
background-color: transparent;
}

> button {
display: flex;
align-items: center;
justify-content: center;
width: 50px;
padding: 0;
aspect-ratio: 1;

> .nlux_snd_icn {
width: 50%;
}

> .nlux_msg_ldr {
display: none;
}
}

> button:disabled {
cursor: not-allowed;
}
}

.nlux_comp_prmpt_box.nlux_prmpt_typing {
button > .nlux_snd_icn {
display: inline-block;
}

button > .nlux_msg_ldr {
display: none;
}
}

.nlux_comp_prmpt_box.nlux_prmpt_submitting,
.nlux_comp_prmpt_box.nlux_prmpt_waiting {
button > .nlux_snd_icn {
display: none;
}

button > .nlux_msg_ldr {
display: inline-block;
}
}
2 changes: 2 additions & 0 deletions packages/css/themes/src/naked/components/WelcomeMessage.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.nlux_comp_wlc_msg {
}
27 changes: 27 additions & 0 deletions packages/css/themes/src/naked/components/animation.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@keyframes nlux-loader-spin {
0%,
100% {
box-shadow: .2em 0px 0 0px currentcolor;
}
12% {
box-shadow: .2em .2em 0 0 currentcolor;
}
25% {
box-shadow: 0 .2em 0 0px currentcolor;
}
37% {
box-shadow: -.2em .2em 0 0 currentcolor;
}
50% {
box-shadow: -.2em 0 0 0 currentcolor;
}
62% {
box-shadow: -.2em -.2em 0 0 currentcolor;
}
75% {
box-shadow: 0px -.2em 0 0 currentcolor;
}
87% {
box-shadow: .2em -.2em 0 0 currentcolor;
}
}
5 changes: 5 additions & 0 deletions packages/css/themes/src/naked/components/colors.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.nlux_root {
--foreground-color: #333333;
--background-color: #f9f9f9;
--border-color: #cccccc;
}
32 changes: 32 additions & 0 deletions packages/js/core/src/comp/ChatPicture/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {DomCreator} from '../../types/dom/DomCreator';
import {ChatPictureProps} from './props';
import {createPhotoContainerFromUrl} from './utils/createPhotoContainerFromUrl';

export const className = 'nlux_comp_cht_pic';

export const createChatPictureDom: DomCreator<ChatPictureProps> = (
props,
): HTMLElement => {
const element = document.createElement('div');
element.classList.add(className);

if (!props.picture && !props.name) {
return element;
}

if (props.name) {
element.title = props.name;
}

// When the picture is an HTMLElement, we clone it and append it to the persona dom
// without any further processing!
if (props.picture && props.picture instanceof HTMLElement) {
element.append(props.picture.cloneNode(true));
return element;
}

// Alternatively, treat the picture as a string representing a URL of the photo to
// be loaded and render the photo accordingly.
element.append(createPhotoContainerFromUrl(props.picture, props.name));
return element;
};
4 changes: 4 additions & 0 deletions packages/js/core/src/comp/ChatPicture/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type ChatPictureProps = {
name?: string;
picture?: string | HTMLElement;
};
28 changes: 28 additions & 0 deletions packages/js/core/src/comp/ChatPicture/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {DomUpdater} from '../../types/dom/DomUpdater';
import {ChatPictureProps} from './props';
import {updateContentOnPictureChange} from './utils/updateContentOnPictureChange';
import {updateNameOnPicture} from './utils/updateNameOnPicture';

export const updateChatPictureDom: DomUpdater<ChatPictureProps> = (
element,
propsBefore,
propsAfter,
): void => {
if (propsBefore.picture === propsAfter.picture && propsBefore.name === propsAfter.name) {
return;
}

if (propsBefore.picture !== propsAfter.picture) {
updateContentOnPictureChange(element, propsBefore, propsAfter);
}

if (propsAfter.name) {
if (propsBefore.name !== propsAfter.name) {
element.title = propsAfter.name;
updateNameOnPicture(element, propsBefore, propsAfter);
}
} else {
element.title = '';
updateNameOnPicture(element, propsBefore, propsAfter);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export const renderedPhotoContainerClassName = 'cht_pic_ctn';
export const renderedPhotoClassName = 'cht_pic_img';
export const renderedInitialsClassName = 'cht_pic_ltr';

export const createPhotoContainerFromUrl = (url: string | undefined, name: string | undefined): HTMLElement => {
// We print the first letter of the name in the persona photo
// Just in case the photo URL does not load
const letterContainer = document.createElement('span');
letterContainer.classList.add(renderedInitialsClassName);
const letter = name && name.length > 0 ? name[0].toUpperCase() : '';
if (letter.length > 0) {
letterContainer.append(letter);
}

const photoContainer = document.createElement('div');
photoContainer.classList.add(renderedPhotoContainerClassName);
photoContainer.append(letterContainer);

// We load the photo in the foreground
if (url) {
const photoDomElement = document.createElement('div');
photoDomElement.classList.add(renderedPhotoClassName);
photoDomElement.style.backgroundImage = `url("${encodeURI(url)}")`;
photoContainer.append(photoDomElement);
}

return photoContainer;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {ChatPictureProps} from '../props';
import {createPhotoContainerFromUrl} from './createPhotoContainerFromUrl';

export const updateContentOnPictureChange = (
element: HTMLElement,
propsBefore: ChatPictureProps,
propsAfter: ChatPictureProps,
): void => {
if (propsBefore.picture === propsAfter.picture) {
return;
}

if (typeof propsAfter.picture === 'string' && typeof propsBefore.picture === 'string') {
// When the picture is a string, we update the photo container with the new URL
const photoDomElement: HTMLElement | null = element.querySelector(
'* > .cht_pic_ctn > .cht_pic_img',
);

if (photoDomElement !== null) {
photoDomElement.style.backgroundImage = `url("${encodeURI(propsAfter.picture)}")`;
}
} else {
if (typeof propsAfter.picture === 'string') {
// When the new picture is a string and the old one is not —
// we create a new photo container from the URL
const newPhotoDomElement = createPhotoContainerFromUrl(
propsAfter.picture,
propsAfter.name,
);
element.replaceChildren(newPhotoDomElement);
} else {
// When the picture is an HTMLElement, we clone it and append it to the persona dom
if (propsAfter.picture) {
element.replaceChildren(propsAfter.picture.cloneNode(true));
} else {
// If the new picture is null, we remove the old one
element.replaceChildren();
}
}
}
};
22 changes: 22 additions & 0 deletions packages/js/core/src/comp/ChatPicture/utils/updateNameOnPicture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {ChatPictureProps} from '../props';

export const updateNameOnPicture = (
element: HTMLElement,
propsBefore: ChatPictureProps,
propsAfter: ChatPictureProps,
): void => {
if (propsBefore.name === propsAfter.name) {
return;
}

if (typeof propsAfter.picture === 'string') {
const letter = propsAfter.name && propsAfter.name.length > 0 ?
propsAfter.name[0].toUpperCase() : '';

const letterContainer = element.querySelector(
'* > .cht_pic_ctn > .cht_pic_ltr',
);

letterContainer?.replaceChildren(letter);
}
};
Loading

0 comments on commit b493dba

Please sign in to comment.