update
This commit is contained in:
56
demo.html
Normal file
56
demo.html
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Social.io Catalog Demo</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
font-family: sans-serif;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.demo-section {
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.component-container {
|
||||
position: relative;
|
||||
height: 600px;
|
||||
margin: 20px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
sio-combox {
|
||||
position: relative !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
right: auto !important;
|
||||
}
|
||||
</style>
|
||||
<script type="module" src="./dist_bundle/bundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="demo-section">
|
||||
<h2>Combox Component</h2>
|
||||
<div class="component-container">
|
||||
<sio-combox></sio-combox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2>FAB with Combox</h2>
|
||||
<div style="position: relative; height: 700px;">
|
||||
<sio-fab showCombox></sio-fab>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -15,12 +15,12 @@
|
||||
"author": "Lossless GmbH",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"@design.estate/dees-catalog": "^1.10.10",
|
||||
"@design.estate/dees-domtools": "^2.3.3",
|
||||
"@design.estate/dees-element": "^2.1.2",
|
||||
"@design.estate/dees-wcctools": "^1.1.1",
|
||||
"@losslessone_private/loint-pubapi": "^1.0.14",
|
||||
"@social.io/interfaces": "^1.2.1",
|
||||
"lucide": "^0.525.0",
|
||||
"rrweb": "2.0.0-alpha.4",
|
||||
"rrweb-player": "1.0.0-alpha.4",
|
||||
"rrweb-snapshot": "2.0.0-alpha.4"
|
||||
|
||||
824
pnpm-lock.yaml
generated
824
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,49 @@
|
||||
import { expect, expectAsync, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
|
||||
import * as socialioCatalog from '../ts_web/index.js';
|
||||
|
||||
tap.test('', async () => {
|
||||
const sioFab: socialioCatalog.SioFab = webhelpers.fixture(webhelpers.html`<sio-fab></sio-fab>`);
|
||||
tap.test('render combox component', async () => {
|
||||
// Create and add combox
|
||||
const combox = new socialioCatalog.SioCombox();
|
||||
combox.style.position = 'relative';
|
||||
combox.style.width = '800px';
|
||||
combox.style.height = '600px';
|
||||
document.body.appendChild(combox);
|
||||
|
||||
await combox.updateComplete;
|
||||
|
||||
expect(combox).toBeInstanceOf(socialioCatalog.SioCombox);
|
||||
|
||||
// Check that the component rendered its content
|
||||
const container = combox.shadowRoot.querySelector('.container');
|
||||
expect(container).toBeTruthy();
|
||||
|
||||
const conversationSelector = combox.shadowRoot.querySelector('sio-conversation-selector');
|
||||
expect(conversationSelector).toBeTruthy();
|
||||
|
||||
const conversationView = combox.shadowRoot.querySelector('sio-conversation-view');
|
||||
expect(conversationView).toBeTruthy();
|
||||
|
||||
console.log('Combox component rendered successfully with all main elements');
|
||||
|
||||
document.body.removeChild(combox);
|
||||
});
|
||||
|
||||
tap.start();
|
||||
tap.test('render fab component', async () => {
|
||||
// Create and add fab
|
||||
const fab = new socialioCatalog.SioFab();
|
||||
document.body.appendChild(fab);
|
||||
|
||||
await fab.updateComplete;
|
||||
expect(fab).toBeInstanceOf(socialioCatalog.SioFab);
|
||||
|
||||
// Check main elements
|
||||
const mainbox = fab.shadowRoot.querySelector('#mainbox');
|
||||
expect(mainbox).toBeTruthy();
|
||||
|
||||
console.log('FAB component rendered successfully');
|
||||
|
||||
document.body.removeChild(fab);
|
||||
});
|
||||
|
||||
tap.start();
|
||||
@@ -108,6 +108,17 @@ export const colors = {
|
||||
dark: 'hsl(212.7 26.8% 83.9%)'
|
||||
},
|
||||
|
||||
// Success colors
|
||||
success: {
|
||||
light: 'hsl(142 76% 36%)',
|
||||
dark: 'hsl(142 70% 45%)'
|
||||
},
|
||||
|
||||
successForeground: {
|
||||
light: 'hsl(0 0% 100%)',
|
||||
dark: 'hsl(0 0% 100%)'
|
||||
},
|
||||
|
||||
// Chart colors
|
||||
chart1: {
|
||||
light: 'hsl(12 76% 61%)',
|
||||
|
||||
@@ -3,9 +3,15 @@ export * from './00colors.js';
|
||||
export * from './00fonts.js';
|
||||
export * from './00tokens.js';
|
||||
|
||||
// Components
|
||||
export * from './sio-fab.js';
|
||||
// Core components
|
||||
export * from './sio-icon.js';
|
||||
export * from './sio-button.js';
|
||||
|
||||
// Conversation components
|
||||
export * from './sio-conversation-selector.js';
|
||||
export * from './sio-conversation-view.js';
|
||||
export * from './sio-combox.js';
|
||||
export * from './sio-subwidget-onboardme.js';
|
||||
export * from './sio-subwidget-conversations.js';
|
||||
|
||||
// Other components
|
||||
export * from './sio-fab.js';
|
||||
export * from './sio-recorder.js';
|
||||
|
||||
259
ts_web/elements/sio-button.ts
Normal file
259
ts_web/elements/sio-button.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
import {
|
||||
DeesElement,
|
||||
html,
|
||||
property,
|
||||
customElement,
|
||||
cssManager,
|
||||
css,
|
||||
unsafeCSS,
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
// Import design tokens
|
||||
import { colors, bdTheme } from './00colors.js';
|
||||
import { spacing, radius, shadows, transitions } from './00tokens.js';
|
||||
import { fontFamilies, typography } from './00fonts.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'sio-button': SioButton;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('sio-button')
|
||||
export class SioButton extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<div style="display: flex; gap: 16px; flex-wrap: wrap; align-items: center;">
|
||||
<sio-button>Default</sio-button>
|
||||
<sio-button type="primary">Primary</sio-button>
|
||||
<sio-button type="destructive">Delete</sio-button>
|
||||
<sio-button type="outline">Outline</sio-button>
|
||||
<sio-button type="ghost">Ghost</sio-button>
|
||||
<sio-button size="sm">Small</sio-button>
|
||||
<sio-button size="lg">Large</sio-button>
|
||||
<sio-button disabled>Disabled</sio-button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@property({ type: String })
|
||||
public text: string = '';
|
||||
|
||||
@property({ type: String })
|
||||
public type: 'default' | 'primary' | 'destructive' | 'outline' | 'ghost' = 'default';
|
||||
|
||||
@property({ type: String })
|
||||
public size: 'sm' | 'default' | 'lg' = 'default';
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public disabled: boolean = false;
|
||||
|
||||
@property({ type: String })
|
||||
public status: 'normal' | 'pending' | 'success' | 'error' = 'normal';
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: inline-block;
|
||||
font-family: ${unsafeCSS(fontFamilies.sans)};
|
||||
}
|
||||
|
||||
:host([disabled]) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
border-radius: ${unsafeCSS(radius.md)};
|
||||
font-weight: 500;
|
||||
transition: ${unsafeCSS(transitions.all)};
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
gap: ${unsafeCSS(spacing[2])};
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Size variants */
|
||||
.button.size-sm {
|
||||
height: 32px;
|
||||
padding: 0 ${unsafeCSS(spacing[3])};
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.button.size-default {
|
||||
height: 36px;
|
||||
padding: 0 ${unsafeCSS(spacing[4])};
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.button.size-lg {
|
||||
height: 44px;
|
||||
padding: 0 ${unsafeCSS(spacing[6])};
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Type variants */
|
||||
.button.default {
|
||||
background: ${bdTheme('secondary')};
|
||||
color: ${bdTheme('secondaryForeground')};
|
||||
border-color: ${bdTheme('border')};
|
||||
}
|
||||
|
||||
.button.default:hover:not(.disabled) {
|
||||
background: ${bdTheme('secondary')};
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.button.default:active:not(.disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.button.primary {
|
||||
background: ${bdTheme('primary')};
|
||||
color: ${bdTheme('primaryForeground')};
|
||||
}
|
||||
|
||||
.button.primary:hover:not(.disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.button.primary:active:not(.disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.button.destructive {
|
||||
background: ${bdTheme('destructive')};
|
||||
color: ${bdTheme('destructiveForeground')};
|
||||
}
|
||||
|
||||
.button.destructive:hover:not(.disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.button.destructive:active:not(.disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.button.outline {
|
||||
background: transparent;
|
||||
color: ${bdTheme('foreground')};
|
||||
border-color: ${bdTheme('border')};
|
||||
}
|
||||
|
||||
.button.outline:hover:not(.disabled) {
|
||||
background: ${bdTheme('accent')};
|
||||
color: ${bdTheme('accentForeground')};
|
||||
}
|
||||
|
||||
.button.outline:active:not(.disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.button.ghost {
|
||||
background: transparent;
|
||||
color: ${bdTheme('foreground')};
|
||||
}
|
||||
|
||||
.button.ghost:hover:not(.disabled) {
|
||||
background: ${bdTheme('accent')};
|
||||
color: ${bdTheme('accentForeground')};
|
||||
}
|
||||
|
||||
.button.ghost:active:not(.disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Status states */
|
||||
.button.pending {
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
position: absolute;
|
||||
left: ${unsafeCSS(spacing[3])};
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.button.success {
|
||||
background: ${bdTheme('success')};
|
||||
color: ${bdTheme('successForeground')};
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.button.error {
|
||||
background: ${bdTheme('destructive')};
|
||||
color: ${bdTheme('destructiveForeground')};
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Disabled state */
|
||||
.button.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Focus state */
|
||||
.button:focus-visible {
|
||||
outline: 2px solid ${bdTheme('ring')};
|
||||
outline-offset: 2px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
const buttonClasses = [
|
||||
'button',
|
||||
this.type === 'primary' ? 'primary' : this.type,
|
||||
`size-${this.size}`,
|
||||
this.disabled ? 'disabled' : '',
|
||||
this.status,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return html`
|
||||
<button
|
||||
class="${buttonClasses}"
|
||||
?disabled=${this.disabled}
|
||||
@click=${this.handleClick}
|
||||
>
|
||||
${this.status === 'pending' ? html`
|
||||
<sio-icon class="spinner" icon="loader" size="16"></sio-icon>
|
||||
` : ''}
|
||||
${this.status === 'success' ? html`
|
||||
<sio-icon icon="check" size="16"></sio-icon>
|
||||
` : ''}
|
||||
${this.status === 'error' ? html`
|
||||
<sio-icon icon="x" size="16"></sio-icon>
|
||||
` : ''}
|
||||
<slot>${this.text}</slot>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
private handleClick(event: MouseEvent) {
|
||||
if (this.disabled || this.status !== 'normal') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch a custom event with any data
|
||||
this.dispatchEvent(new CustomEvent('click', {
|
||||
detail: { originalEvent: event },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,30 @@ import {
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
cssManager,
|
||||
css,
|
||||
unsafeCSS,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
|
||||
import * as deesCatalog from '@design.estate/dees-catalog';
|
||||
deesCatalog;
|
||||
// Import design tokens
|
||||
import { colors, bdTheme } from './00colors.js';
|
||||
import { spacing, radius, shadows, transitions } from './00tokens.js';
|
||||
import { fontFamilies, typography } from './00fonts.js';
|
||||
|
||||
// Import components
|
||||
import { SioConversationSelector, type IConversation } from './sio-conversation-selector.js';
|
||||
import { SioConversationView, type IMessage, type IConversationData } from './sio-conversation-view.js';
|
||||
|
||||
// Make sure components are loaded
|
||||
SioConversationSelector;
|
||||
SioConversationView;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'sio-combox': SioCombox;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('sio-combox')
|
||||
export class SioCombox extends DeesElement {
|
||||
@@ -18,183 +37,231 @@ export class SioCombox extends DeesElement {
|
||||
@property({ type: Object })
|
||||
public referenceObject: HTMLElement;
|
||||
|
||||
/**
|
||||
* computes the button offset
|
||||
*/
|
||||
public cssComputeHeight() {
|
||||
let height = window.innerHeight < 760 ? window.innerHeight : 760;
|
||||
if (!this.referenceObject) {
|
||||
console.log('SioCombox: no reference object set');
|
||||
}
|
||||
if (this.referenceObject) {
|
||||
console.log(`referenceObject height is ${this.referenceObject.clientHeight}`);
|
||||
height = height - (this.referenceObject.clientHeight + 60);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
@state()
|
||||
private selectedConversationId: string | null = null;
|
||||
|
||||
public cssComputeInnerScroll() {
|
||||
console.log(
|
||||
`SioCombox clientHeight: ${this.shadowRoot.querySelector('.mainbox').clientHeight}`
|
||||
);
|
||||
console.log(
|
||||
`SioCombox content scrollheight is: ${
|
||||
this.shadowRoot.querySelector('.contentbox').clientHeight
|
||||
}`
|
||||
);
|
||||
if (
|
||||
this.shadowRoot.querySelector('.mainbox').clientHeight <
|
||||
this.shadowRoot.querySelector('.contentbox').clientHeight
|
||||
) {
|
||||
(this.shadowRoot.querySelector('.mainbox') as HTMLElement).style.overflowY = 'scroll';
|
||||
} else {
|
||||
(this.shadowRoot.querySelector('.mainbox') as HTMLElement).style.overflowY = 'hidden';
|
||||
@state()
|
||||
private conversations: IConversation[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Technical Support',
|
||||
lastMessage: 'Thanks for your help with the login issue!',
|
||||
time: '2 min ago',
|
||||
unread: true,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Billing Question',
|
||||
lastMessage: 'I need help understanding my invoice',
|
||||
time: '1 hour ago',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Feature Request',
|
||||
lastMessage: 'That would be great! Looking forward to it',
|
||||
time: 'Yesterday',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: 'General Inquiry',
|
||||
lastMessage: 'Thank you for the information',
|
||||
time: '2 days ago',
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@state()
|
||||
private messages: { [conversationId: string]: IMessage[] } = {
|
||||
'1': [
|
||||
{ id: '1', text: 'Hi, I\'m having trouble logging in', sender: 'user', time: '10:00 AM' },
|
||||
{ id: '2', text: 'I can help you with that. Can you tell me what error you\'re seeing?', sender: 'support', time: '10:02 AM' },
|
||||
{ id: '3', text: 'It says "Invalid credentials" but I\'m sure my password is correct', sender: 'user', time: '10:03 AM' },
|
||||
{ id: '4', text: 'Let me check your account. Please try resetting your password using the forgot password link.', sender: 'support', time: '10:05 AM' },
|
||||
{ id: '5', text: 'Thanks for your help with the login issue!', sender: 'user', time: '10:10 AM' },
|
||||
],
|
||||
'2': [
|
||||
{ id: '1', text: 'I need help understanding my invoice', sender: 'user', time: '9:00 AM' },
|
||||
{ id: '2', text: 'I\'d be happy to help explain your invoice. Which part is unclear?', sender: 'support', time: '9:05 AM' },
|
||||
],
|
||||
'3': [
|
||||
{ id: '1', text: 'I\'d love to see dark mode support in your app!', sender: 'user', time: 'Yesterday' },
|
||||
{ id: '2', text: 'Thanks for the suggestion! We\'re actually working on dark mode and it should be available next month.', sender: 'support', time: 'Yesterday' },
|
||||
{ id: '3', text: 'That would be great! Looking forward to it', sender: 'user', time: 'Yesterday' },
|
||||
],
|
||||
'4': [
|
||||
{ id: '1', text: 'Can you tell me more about your enterprise plans?', sender: 'user', time: '2 days ago' },
|
||||
{ id: '2', text: 'Of course! Our enterprise plans include advanced features like SSO, dedicated support, and custom integrations.', sender: 'support', time: '2 days ago' },
|
||||
{ id: '3', text: 'Thank you for the information', sender: 'user', time: '2 days ago' },
|
||||
]
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
domtools.DomTools.setupDomTools();
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
${domtools.elementBasic.styles}
|
||||
<style>
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 600px;
|
||||
width: 800px;
|
||||
background: ${bdTheme('background')};
|
||||
border-radius: ${unsafeCSS(radius.xl)};
|
||||
border: 1px solid ${bdTheme('border')};
|
||||
box-shadow: ${unsafeCSS(shadows.xl)};
|
||||
overflow: hidden;
|
||||
font-family: ${unsafeCSS(fontFamilies.sans)};
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Responsive layout */
|
||||
@media (max-width: 600px) {
|
||||
:host {
|
||||
overflow: hidden;
|
||||
font-family: 'Dees Sans';
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: ${this.cssComputeHeight()}px;
|
||||
width: 375px;
|
||||
background: ${this.goBright ? '#eeeeee' : '#000000'};
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(250, 250, 250, 0.2);
|
||||
right: 0px;
|
||||
z-index: 10000;
|
||||
box-shadow: 0px 0px 5px ${this.goBright ? 'rgba(0, 0, 0, 0.3)' : 'rgba(0, 0, 0, 0.5)'};
|
||||
color: ${this.goBright ? '#333' : '#ccc'};
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mainbox {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
overscroll-behavior: contain;
|
||||
padding-bottom: 80px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.toppanel {
|
||||
height: 200px;
|
||||
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.5);
|
||||
padding: 20px;
|
||||
--bg-color: ${this.goBright ? '#00000050' : '#ffffff30'};
|
||||
--dot-color: #ffffff00;
|
||||
--dot-size: 1px;
|
||||
--dot-space: 6px;
|
||||
|
||||
background: linear-gradient(45deg, var(--bg-color) 1px, var(--dot-color) 1px) top left;
|
||||
background-size: var(--dot-space) var(--dot-space);
|
||||
margin-bottom: -50px;
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#greeting {
|
||||
padding-top: 50px;
|
||||
font-family: 'Dees Sans';
|
||||
margin: 0px;
|
||||
font-size: 30px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
#callToAction {
|
||||
font-family: 'Dees Sans';
|
||||
margin: 0px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.quicktabs {
|
||||
sio-conversation-selector {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
bottom: 30px;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
padding-bottom: 16px;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
background-image: linear-gradient(to bottom, ${cssManager.bdTheme('#eeeeeb00', 'rgba(0, 0, 0, 0)')} 0%, ${cssManager.bdTheme('#eeeeebff', 'rgba(0, 0, 0, 1)')} 50%);
|
||||
padding-top: 24px;
|
||||
height: 100%;
|
||||
transition: left 300ms ease;
|
||||
}
|
||||
|
||||
.quicktabs .quicktab {
|
||||
text-align: center;
|
||||
sio-conversation-view {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
.quicktabs .quicktab .quicktabicon {
|
||||
font-size: 20px;
|
||||
margin-bottom: 8px;
|
||||
height: 100%;
|
||||
transition: left 300ms ease;
|
||||
}
|
||||
|
||||
.quicktabs .quicktab .quicktabtext {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
/* Mobile navigation states */
|
||||
.container.show-list sio-conversation-selector {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.container.show-list sio-conversation-view {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.container.show-conversation sio-conversation-selector {
|
||||
left: -100%;
|
||||
}
|
||||
|
||||
.container.show-conversation sio-conversation-view {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 601px) {
|
||||
sio-conversation-selector {
|
||||
width: 320px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
sio-conversation-view {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
const selectedConversation = this.selectedConversationId
|
||||
? this.conversations.find(c => c.id === this.selectedConversationId)
|
||||
: null;
|
||||
|
||||
const conversationData: IConversationData | null = selectedConversation
|
||||
? {
|
||||
id: selectedConversation.id,
|
||||
title: selectedConversation.title,
|
||||
messages: this.messages[selectedConversation.id] || []
|
||||
}
|
||||
: null;
|
||||
|
||||
const containerClass = this.selectedConversationId ? 'show-conversation' : 'show-list';
|
||||
|
||||
return html`
|
||||
<div class="container ${containerClass}">
|
||||
<sio-conversation-selector
|
||||
.conversations=${this.conversations}
|
||||
.selectedConversationId=${this.selectedConversationId}
|
||||
@conversation-selected=${this.handleConversationSelected}
|
||||
></sio-conversation-selector>
|
||||
|
||||
|
||||
|
||||
.brandingbox {
|
||||
z-index: 101;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
font-size: 12px;
|
||||
padding: 8px;
|
||||
border-top: 1px solid rgba(250, 250, 250, 0.1);
|
||||
font-family: 'Dees Sans';
|
||||
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
|
||||
background: ${this.goBright ? '#EEE' : '#000'};
|
||||
color: ${this.goBright ? '#333' : '#777'};
|
||||
}
|
||||
</style>
|
||||
<div class="mainbox">
|
||||
<div class="contentbox">
|
||||
<div class="toppanel">
|
||||
<div id="greeting">Hello :)</div>
|
||||
<div id="callToAction">Ask us anything or share your feedback!</div>
|
||||
</div>
|
||||
<sio-subwidget-conversations></sio-subwidget-conversations>
|
||||
<sio-subwidget-onboardme></sio-subwidget-onboardme>
|
||||
</div>
|
||||
<sio-conversation-view
|
||||
.conversation=${conversationData}
|
||||
@back=${this.handleBack}
|
||||
@send-message=${this.handleSendMessage}
|
||||
></sio-conversation-view>
|
||||
</div>
|
||||
<div class="quicktabs">
|
||||
<div class="quicktab">
|
||||
<div class="quicktabicon">
|
||||
<dees-icon iconFA="message"></dees-icon>
|
||||
</div>
|
||||
<div class="quicktabtext">Conversations</div>
|
||||
</div>
|
||||
<div class="quicktab">
|
||||
<div class="quicktabicon">
|
||||
<dees-icon iconFA="mugHot"></dees-icon>
|
||||
</div>
|
||||
<div class="quicktabtext">Onboarding</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="brandingbox">powered by social.io</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async updated() {
|
||||
this.cssComputeHeight();
|
||||
window.requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
this.cssComputeInnerScroll();
|
||||
}, 200);
|
||||
});
|
||||
private handleConversationSelected(event: CustomEvent) {
|
||||
const conversation = event.detail.conversation as IConversation;
|
||||
this.selectedConversationId = conversation.id;
|
||||
|
||||
// Mark conversation as read
|
||||
const convIndex = this.conversations.findIndex(c => c.id === conversation.id);
|
||||
if (convIndex !== -1) {
|
||||
this.conversations[convIndex] = { ...this.conversations[convIndex], unread: false };
|
||||
this.conversations = [...this.conversations];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleBack() {
|
||||
// For mobile view, go back to conversation list
|
||||
this.selectedConversationId = null;
|
||||
}
|
||||
|
||||
private handleSendMessage(event: CustomEvent) {
|
||||
const message = event.detail.message as IMessage;
|
||||
const conversationId = this.selectedConversationId;
|
||||
|
||||
if (conversationId) {
|
||||
// Add message to the conversation
|
||||
if (!this.messages[conversationId]) {
|
||||
this.messages[conversationId] = [];
|
||||
}
|
||||
this.messages[conversationId] = [...this.messages[conversationId], message];
|
||||
this.messages = { ...this.messages };
|
||||
|
||||
// Update conversation's last message
|
||||
const convIndex = this.conversations.findIndex(c => c.id === conversationId);
|
||||
if (convIndex !== -1) {
|
||||
this.conversations[convIndex] = {
|
||||
...this.conversations[convIndex],
|
||||
lastMessage: message.text,
|
||||
time: 'Just now'
|
||||
};
|
||||
// Move conversation to top
|
||||
const [conv] = this.conversations.splice(convIndex, 1);
|
||||
this.conversations = [conv, ...this.conversations];
|
||||
}
|
||||
|
||||
// Simulate a response after a delay (remove in production)
|
||||
setTimeout(() => {
|
||||
const responseMessage: IMessage = {
|
||||
id: Date.now().toString(),
|
||||
text: 'Thanks for your message! We\'ll get back to you shortly.',
|
||||
sender: 'support',
|
||||
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
};
|
||||
|
||||
this.messages[conversationId] = [...this.messages[conversationId], responseMessage];
|
||||
this.messages = { ...this.messages };
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
267
ts_web/elements/sio-conversation-selector.ts
Normal file
267
ts_web/elements/sio-conversation-selector.ts
Normal file
@@ -0,0 +1,267 @@
|
||||
import {
|
||||
DeesElement,
|
||||
property,
|
||||
html,
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
cssManager,
|
||||
css,
|
||||
unsafeCSS,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
// Import design tokens
|
||||
import { colors, bdTheme } from './00colors.js';
|
||||
import { spacing, radius, shadows, transitions } from './00tokens.js';
|
||||
import { fontFamilies, typography } from './00fonts.js';
|
||||
|
||||
// Types
|
||||
export interface IConversation {
|
||||
id: string;
|
||||
title: string;
|
||||
lastMessage: string;
|
||||
time: string;
|
||||
unread?: boolean;
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'sio-conversation-selector': SioConversationSelector;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('sio-conversation-selector')
|
||||
export class SioConversationSelector extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<sio-conversation-selector style="width: 320px; height: 600px;"></sio-conversation-selector>
|
||||
`;
|
||||
|
||||
@property({ type: Array })
|
||||
public conversations: IConversation[] = [];
|
||||
|
||||
@property({ type: String })
|
||||
public selectedConversationId: string | null = null;
|
||||
|
||||
@state()
|
||||
private searchQuery: string = '';
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: ${bdTheme('card')};
|
||||
border-right: 1px solid ${bdTheme('border')};
|
||||
font-family: ${unsafeCSS(fontFamilies.sans)};
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: ${unsafeCSS(spacing[4])};
|
||||
border-bottom: 1px solid ${bdTheme('border')};
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.5;
|
||||
font-weight: 600;
|
||||
margin: 0 0 ${unsafeCSS(spacing[3])} 0;
|
||||
color: ${bdTheme('foreground')};
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: ${unsafeCSS(spacing[2])} ${unsafeCSS(spacing[10])} ${unsafeCSS(spacing[2])} ${unsafeCSS(spacing[3])};
|
||||
background: ${bdTheme('muted')};
|
||||
border: 1px solid ${bdTheme('border')};
|
||||
border-radius: ${unsafeCSS(radius.md)};
|
||||
font-size: 14px;
|
||||
color: ${bdTheme('foreground')};
|
||||
outline: none;
|
||||
transition: ${unsafeCSS(transitions.all)};
|
||||
font-family: ${unsafeCSS(fontFamilies.sans)};
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: ${bdTheme('mutedForeground')};
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
background: ${bdTheme('background')};
|
||||
border-color: ${bdTheme('ring')};
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: ${unsafeCSS(spacing[3])};
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: ${bdTheme('mutedForeground')};
|
||||
}
|
||||
|
||||
.conversation-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: ${unsafeCSS(spacing[2])};
|
||||
}
|
||||
|
||||
.conversation-item {
|
||||
padding: ${unsafeCSS(spacing[3])};
|
||||
margin-bottom: ${unsafeCSS(spacing[2])};
|
||||
background: ${bdTheme('background')};
|
||||
border: 1px solid ${bdTheme('border')};
|
||||
border-radius: ${unsafeCSS(radius.md)};
|
||||
cursor: pointer;
|
||||
transition: ${unsafeCSS(transitions.all)};
|
||||
}
|
||||
|
||||
.conversation-item:hover {
|
||||
background: ${bdTheme('accent')};
|
||||
}
|
||||
|
||||
.conversation-item.selected {
|
||||
background: ${bdTheme('accent')};
|
||||
border-color: ${bdTheme('primary')};
|
||||
}
|
||||
|
||||
.conversation-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: ${unsafeCSS(spacing[1])};
|
||||
}
|
||||
|
||||
.conversation-title {
|
||||
font-weight: 600;
|
||||
color: ${bdTheme('foreground')};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: ${unsafeCSS(spacing[2])};
|
||||
}
|
||||
|
||||
.conversation-time {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: ${bdTheme('mutedForeground')};
|
||||
}
|
||||
|
||||
.conversation-preview {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
color: ${bdTheme('mutedForeground')};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.unread-dot {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: ${bdTheme('primary')};
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: ${unsafeCSS(spacing[4])};
|
||||
text-align: center;
|
||||
color: ${bdTheme('mutedForeground')};
|
||||
gap: ${unsafeCSS(spacing[3])};
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
.conversation-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.conversation-list::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.conversation-list::-webkit-scrollbar-thumb {
|
||||
background: ${bdTheme('border')};
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.conversation-list::-webkit-scrollbar-thumb:hover {
|
||||
background: ${bdTheme('mutedForeground')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
const filteredConversations = this.conversations.filter(conv =>
|
||||
conv.title.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
|
||||
conv.lastMessage.toLowerCase().includes(this.searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="header">
|
||||
<h2 class="title">Messages</h2>
|
||||
<div class="search-box">
|
||||
<input
|
||||
type="text"
|
||||
class="search-input"
|
||||
placeholder="Search conversations..."
|
||||
.value=${this.searchQuery}
|
||||
@input=${(e: Event) => this.searchQuery = (e.target as HTMLInputElement).value}
|
||||
/>
|
||||
<sio-icon class="search-icon" icon="search" size="16"></sio-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${filteredConversations.length > 0 ? html`
|
||||
<div class="conversation-list">
|
||||
${filteredConversations.map(conv => html`
|
||||
<div
|
||||
class="conversation-item ${this.selectedConversationId === conv.id ? 'selected' : ''}"
|
||||
@click=${() => this.selectConversation(conv)}
|
||||
>
|
||||
<div class="conversation-header">
|
||||
<span class="conversation-title">
|
||||
${conv.title}
|
||||
${conv.unread ? html`<span class="unread-dot"></span>` : ''}
|
||||
</span>
|
||||
<span class="conversation-time">${conv.time}</span>
|
||||
</div>
|
||||
<div class="conversation-preview">${conv.lastMessage}</div>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
` : html`
|
||||
<div class="empty-state">
|
||||
<sio-icon class="empty-icon" icon="message-square"></sio-icon>
|
||||
<h3>${this.searchQuery ? 'No matching conversations' : 'No conversations yet'}</h3>
|
||||
<p>${this.searchQuery ? 'Try a different search term' : 'Start a new conversation to get started'}</p>
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
private selectConversation(conversation: IConversation) {
|
||||
this.selectedConversationId = conversation.id;
|
||||
|
||||
// Dispatch event for parent components
|
||||
this.dispatchEvent(new CustomEvent('conversation-selected', {
|
||||
detail: { conversation },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
}
|
||||
}
|
||||
413
ts_web/elements/sio-conversation-view.ts
Normal file
413
ts_web/elements/sio-conversation-view.ts
Normal file
@@ -0,0 +1,413 @@
|
||||
import {
|
||||
DeesElement,
|
||||
property,
|
||||
html,
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
cssManager,
|
||||
css,
|
||||
unsafeCSS,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
// Import design tokens
|
||||
import { colors, bdTheme } from './00colors.js';
|
||||
import { spacing, radius, shadows, transitions } from './00tokens.js';
|
||||
import { fontFamilies, typography } from './00fonts.js';
|
||||
|
||||
// Types
|
||||
export interface IMessage {
|
||||
id: string;
|
||||
text: string;
|
||||
sender: 'user' | 'support';
|
||||
time: string;
|
||||
status?: 'sending' | 'sent' | 'delivered' | 'read';
|
||||
}
|
||||
|
||||
export interface IConversationData {
|
||||
id: string;
|
||||
title: string;
|
||||
messages: IMessage[];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'sio-conversation-view': SioConversationView;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('sio-conversation-view')
|
||||
export class SioConversationView extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<sio-conversation-view style="width: 600px; height: 600px;"></sio-conversation-view>
|
||||
`;
|
||||
|
||||
@property({ type: Object })
|
||||
public conversation: IConversationData | null = null;
|
||||
|
||||
@state()
|
||||
private messageText: string = '';
|
||||
|
||||
@state()
|
||||
private isTyping: boolean = false;
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: ${bdTheme('background')};
|
||||
font-family: ${unsafeCSS(fontFamilies.sans)};
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: ${unsafeCSS(spacing[4])};
|
||||
border-bottom: 1px solid ${bdTheme('border')};
|
||||
background: ${bdTheme('card')};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: ${unsafeCSS(spacing[3])};
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.back-button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.5;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: ${bdTheme('foreground')};
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: ${unsafeCSS(spacing[2])};
|
||||
}
|
||||
|
||||
.messages-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: ${unsafeCSS(spacing[4])};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${unsafeCSS(spacing[3])};
|
||||
}
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: ${unsafeCSS(spacing[3])};
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
.message.user {
|
||||
align-self: flex-end;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: ${unsafeCSS(spacing[3])};
|
||||
border-radius: ${unsafeCSS(radius.lg)};
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message.support .message-bubble {
|
||||
background: ${bdTheme('muted')};
|
||||
color: ${bdTheme('foreground')};
|
||||
border-bottom-left-radius: ${unsafeCSS(spacing[1])};
|
||||
}
|
||||
|
||||
.message.user .message-bubble {
|
||||
background: ${bdTheme('primary')};
|
||||
color: ${bdTheme('primaryForeground')};
|
||||
border-bottom-right-radius: ${unsafeCSS(spacing[1])};
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: ${bdTheme('mutedForeground')};
|
||||
margin-top: ${unsafeCSS(spacing[1])};
|
||||
}
|
||||
|
||||
.message.user .message-time {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.typing-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: ${unsafeCSS(spacing[1])};
|
||||
padding: ${unsafeCSS(spacing[2])} ${unsafeCSS(spacing[3])};
|
||||
background: ${bdTheme('muted')};
|
||||
border-radius: ${unsafeCSS(radius.lg)};
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.typing-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: ${bdTheme('mutedForeground')};
|
||||
border-radius: 50%;
|
||||
animation: typing 1.4s infinite;
|
||||
}
|
||||
|
||||
.typing-dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.typing-dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes typing {
|
||||
0%, 60%, 100% {
|
||||
opacity: 0.3;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
30% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.input-container {
|
||||
padding: ${unsafeCSS(spacing[4])};
|
||||
border-top: 1px solid ${bdTheme('border')};
|
||||
background: ${bdTheme('card')};
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
gap: ${unsafeCSS(spacing[2])};
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-input {
|
||||
flex: 1;
|
||||
min-height: 40px;
|
||||
max-height: 120px;
|
||||
padding: ${unsafeCSS(spacing[2])} ${unsafeCSS(spacing[3])};
|
||||
background: ${bdTheme('background')};
|
||||
border: 1px solid ${bdTheme('border')};
|
||||
border-radius: ${unsafeCSS(radius.md)};
|
||||
font-size: 14px;
|
||||
color: ${bdTheme('foreground')};
|
||||
outline: none;
|
||||
resize: none;
|
||||
font-family: ${unsafeCSS(fontFamilies.sans)};
|
||||
line-height: 1.5;
|
||||
transition: ${unsafeCSS(transitions.all)};
|
||||
}
|
||||
|
||||
.message-input::placeholder {
|
||||
color: ${bdTheme('mutedForeground')};
|
||||
}
|
||||
|
||||
.message-input:focus {
|
||||
border-color: ${bdTheme('ring')};
|
||||
}
|
||||
|
||||
.input-actions {
|
||||
display: flex;
|
||||
gap: ${unsafeCSS(spacing[1])};
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: ${unsafeCSS(spacing[4])};
|
||||
padding: ${unsafeCSS(spacing[8])};
|
||||
text-align: center;
|
||||
color: ${bdTheme('mutedForeground')};
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
.messages-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.messages-container::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.messages-container::-webkit-scrollbar-thumb {
|
||||
background: ${bdTheme('border')};
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.messages-container::-webkit-scrollbar-thumb:hover {
|
||||
background: ${bdTheme('mutedForeground')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
if (!this.conversation) {
|
||||
return html`
|
||||
<div class="empty-state">
|
||||
<sio-icon class="empty-icon" icon="message-square"></sio-icon>
|
||||
<h3>Select a conversation</h3>
|
||||
<p>Choose a conversation from the sidebar to start messaging</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="header">
|
||||
<sio-button
|
||||
class="back-button"
|
||||
type="ghost"
|
||||
size="sm"
|
||||
@click=${this.handleBack}
|
||||
>
|
||||
<sio-icon icon="arrow-left" size="16"></sio-icon>
|
||||
</sio-button>
|
||||
<h3 class="header-title">${this.conversation.title}</h3>
|
||||
<div class="header-actions">
|
||||
<sio-button type="ghost" size="sm">
|
||||
<sio-icon icon="phone" size="16"></sio-icon>
|
||||
</sio-button>
|
||||
<sio-button type="ghost" size="sm">
|
||||
<sio-icon icon="more-vertical" size="16"></sio-icon>
|
||||
</sio-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="messages-container" id="messages">
|
||||
${this.conversation.messages.map(msg => html`
|
||||
<div class="message ${msg.sender}">
|
||||
<div class="message-content">
|
||||
<div class="message-bubble">
|
||||
${msg.text}
|
||||
</div>
|
||||
<div class="message-time">${msg.time}</div>
|
||||
</div>
|
||||
</div>
|
||||
`)}
|
||||
|
||||
${this.isTyping ? html`
|
||||
<div class="message support">
|
||||
<div class="typing-indicator">
|
||||
<div class="typing-dot"></div>
|
||||
<div class="typing-dot"></div>
|
||||
<div class="typing-dot"></div>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div class="input-container">
|
||||
<div class="input-wrapper">
|
||||
<textarea
|
||||
class="message-input"
|
||||
placeholder="Type a message..."
|
||||
.value=${this.messageText}
|
||||
@input=${this.handleInput}
|
||||
@keydown=${this.handleKeyDown}
|
||||
rows="1"
|
||||
></textarea>
|
||||
<div class="input-actions">
|
||||
<sio-button type="ghost" size="sm">
|
||||
<sio-icon icon="paperclip" size="16"></sio-icon>
|
||||
</sio-button>
|
||||
<sio-button
|
||||
type="primary"
|
||||
size="sm"
|
||||
?disabled=${!this.messageText.trim()}
|
||||
@click=${this.sendMessage}
|
||||
>
|
||||
<sio-icon icon="send" size="16"></sio-icon>
|
||||
</sio-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private handleBack() {
|
||||
this.dispatchEvent(new CustomEvent('back', {
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
}
|
||||
|
||||
private handleInput(e: Event) {
|
||||
const textarea = e.target as HTMLTextAreaElement;
|
||||
this.messageText = textarea.value;
|
||||
|
||||
// Auto-resize textarea
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
|
||||
}
|
||||
|
||||
private handleKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
this.sendMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private sendMessage() {
|
||||
if (!this.messageText.trim()) return;
|
||||
|
||||
const message: IMessage = {
|
||||
id: Date.now().toString(),
|
||||
text: this.messageText.trim(),
|
||||
sender: 'user',
|
||||
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
||||
status: 'sending'
|
||||
};
|
||||
|
||||
// Dispatch event for parent to handle
|
||||
this.dispatchEvent(new CustomEvent('send-message', {
|
||||
detail: { message },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
|
||||
// Clear input
|
||||
this.messageText = '';
|
||||
const textarea = this.shadowRoot?.querySelector('.message-input') as HTMLTextAreaElement;
|
||||
if (textarea) {
|
||||
textarea.style.height = 'auto';
|
||||
}
|
||||
|
||||
// Simulate typing indicator (remove in production)
|
||||
setTimeout(() => {
|
||||
this.isTyping = true;
|
||||
setTimeout(() => {
|
||||
this.isTyping = false;
|
||||
}, 2000);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
public updated() {
|
||||
// Scroll to bottom when new messages arrive
|
||||
const container = this.shadowRoot?.querySelector('#messages');
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,14 @@ import {
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
|
||||
import { SioCombox } from './sio-combox.js';
|
||||
import { SioIcon } from './sio-icon.js';
|
||||
SioCombox;
|
||||
SioIcon;
|
||||
|
||||
// Import design tokens
|
||||
import { colors, bdTheme } from './00colors.js';
|
||||
import { spacing, radius, shadows, transitions, sizes } from './00tokens.js';
|
||||
import { fontFamilies, typography } from './00fonts.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -20,7 +27,7 @@ declare global {
|
||||
|
||||
@customElement('sio-fab')
|
||||
export class SioFab extends DeesElement {
|
||||
@property()
|
||||
@property({ type: Boolean })
|
||||
public showCombox = false;
|
||||
|
||||
public static demo = () => html` <sio-fab .showCombox=${true}></sio-fab> `;
|
||||
@@ -45,30 +52,31 @@ export class SioFab extends DeesElement {
|
||||
}
|
||||
|
||||
#mainbox {
|
||||
transition: all 0.2s;
|
||||
transition: ${transitions.all};
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
|
||||
line-height: 60px;
|
||||
height: 56px;
|
||||
width: 56px;
|
||||
box-shadow: ${cssManager.bdTheme(shadows.md, shadows.lg)};
|
||||
line-height: 56px;
|
||||
text-align: center;
|
||||
color: #ccc;
|
||||
cursor: pointer;
|
||||
background: ${this.goBright
|
||||
? 'linear-gradient(-45deg, #eeeeeb, #eeeeeb)'
|
||||
: 'linear-gradient(-45deg, #222222, #333333)'};
|
||||
border-radius: 50% 50% 50% 50%;
|
||||
background: ${bdTheme('primary')};
|
||||
color: ${bdTheme('primaryForeground')};
|
||||
border-radius: ${radius.full};
|
||||
user-select: none;
|
||||
border: 1px solid ${bdTheme('border')};
|
||||
}
|
||||
|
||||
#mainbox:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: ${cssManager.bdTheme(shadows.lg, shadows.xl)};
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
#mainbox:active {
|
||||
transform: scale(0.95);
|
||||
transform: translateY(0);
|
||||
box-shadow: ${cssManager.bdTheme(shadows.sm, shadows.md)};
|
||||
}
|
||||
|
||||
#mainbox .icon {
|
||||
@@ -77,7 +85,7 @@ export class SioFab extends DeesElement {
|
||||
left: 0px;
|
||||
will-change: transform;
|
||||
transform: ${this.showCombox ? 'rotate(0deg)' : 'rotate(-360deg)'};
|
||||
transition: all 0.2s;
|
||||
transition: ${transitions.transform};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
@@ -85,11 +93,10 @@ export class SioFab extends DeesElement {
|
||||
-khtml-user-drag: none;
|
||||
-moz-user-drag: none;
|
||||
-o-user-drag: none;
|
||||
user-drag: none;
|
||||
}
|
||||
|
||||
#mainbox .icon img {
|
||||
filter: grayscale(1) ${cssManager.bdTheme('invert(1)', '')};
|
||||
filter: ${cssManager.bdTheme('brightness(0) invert(1)', 'brightness(1)')};
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 0px;
|
||||
@@ -98,7 +105,7 @@ export class SioFab extends DeesElement {
|
||||
transform: scale(0.2, 0.2) translateY(-5px);
|
||||
}
|
||||
#mainbox .icon.open:hover img {
|
||||
filter: grayscale(0);
|
||||
filter: ${cssManager.bdTheme('brightness(0) invert(1)', 'brightness(1.2)')};
|
||||
}
|
||||
|
||||
#mainbox .icon.open {
|
||||
@@ -110,40 +117,52 @@ export class SioFab extends DeesElement {
|
||||
opacity: ${this.showCombox ? '1' : '0'};
|
||||
pointer-events: ${this.showCombox ? 'all' : 'none'};
|
||||
}
|
||||
#mainbox .icon.close:hover dees-icon {
|
||||
color: ${cssManager.bdTheme('#111', '#fff')};
|
||||
#mainbox .icon.close:hover sio-icon {
|
||||
color: ${bdTheme('primaryForeground')};
|
||||
}
|
||||
|
||||
#mainbox .icon.open dees-icon {
|
||||
#mainbox .icon.open sio-icon {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 32px;
|
||||
color: ${cssManager.bdTheme('#777', '#999')};
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
transform: translateY(2px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${bdTheme('primaryForeground')};
|
||||
}
|
||||
|
||||
#mainbox .icon.close dees-icon {
|
||||
#mainbox .icon.close sio-icon {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 24px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
color: ${cssManager.bdTheme('#666', '#CCC')};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${bdTheme('primaryForeground')};
|
||||
}
|
||||
|
||||
#comboxContainer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#comboxContainer sio-combox {
|
||||
transition: transform 0.2s, opacity 0.2s;
|
||||
position: absolute;
|
||||
bottom: calc(56px + ${spacing[4]});
|
||||
right: 0;
|
||||
transition: ${transitions.all};
|
||||
will-change: transform;
|
||||
transform: translateY(20px);
|
||||
bottom: 80px;
|
||||
transform: translateY(${spacing[5]});
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#comboxContainer.show {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
#comboxContainer.show sio-combox {
|
||||
transform: translateY(0px);
|
||||
opacity: 1;
|
||||
@@ -152,15 +171,15 @@ export class SioFab extends DeesElement {
|
||||
</style>
|
||||
<div id="mainbox" @click=${this.toggleCombox}>
|
||||
<div class="icon open">
|
||||
<dees-icon iconFA="message"></dees-icon>
|
||||
<sio-icon icon="message-square" size="28"></sio-icon>
|
||||
<img src="https://assetbroker.lossless.one/brandfiles/00general/favicon_socialio.svg" />
|
||||
</div>
|
||||
<div class="icon close">
|
||||
<dees-icon iconFa="xmark"></dees-icon>
|
||||
<sio-icon icon="x" size="22"></sio-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div id="comboxContainer" class="${this.showCombox ? 'show' : null}">
|
||||
<sio-combox></sio-combox>
|
||||
<sio-combox @close=${() => this.showCombox = false}></sio-combox>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
163
ts_web/elements/sio-icon.ts
Normal file
163
ts_web/elements/sio-icon.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import {
|
||||
DeesElement,
|
||||
html,
|
||||
property,
|
||||
customElement,
|
||||
cssManager,
|
||||
css,
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as lucideIcons from 'lucide';
|
||||
import { createElement } from 'lucide';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'sio-icon': SioIcon;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('sio-icon')
|
||||
export class SioIcon extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<div style="display: flex; gap: 16px; align-items: center;">
|
||||
<sio-icon icon="search"></sio-icon>
|
||||
<sio-icon icon="message-square" color="#3b82f6"></sio-icon>
|
||||
<sio-icon icon="x" size="32"></sio-icon>
|
||||
<sio-icon icon="send" strokeWidth="3"></sio-icon>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@property({ type: String })
|
||||
public icon: string;
|
||||
|
||||
@property({ type: Number })
|
||||
public size: number = 24;
|
||||
|
||||
@property({ type: String })
|
||||
public color: string = 'currentColor';
|
||||
|
||||
@property({ type: Number })
|
||||
public strokeWidth: number = 2;
|
||||
|
||||
// Cache for rendered icons
|
||||
private static iconCache = new Map<string, string>();
|
||||
private static readonly MAX_CACHE_SIZE = 100;
|
||||
|
||||
// Track last rendered properties to avoid unnecessary updates
|
||||
private lastIcon: string | null = null;
|
||||
private lastSize: number | null = null;
|
||||
private lastColor: string | null = null;
|
||||
private lastStrokeWidth: number | null = null;
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#iconContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#iconContainer svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div id="iconContainer" style="width: ${this.size}px; height: ${this.size}px;"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
public updated() {
|
||||
// Check if we need to update
|
||||
if (
|
||||
this.lastIcon === this.icon &&
|
||||
this.lastSize === this.size &&
|
||||
this.lastColor === this.color &&
|
||||
this.lastStrokeWidth === this.strokeWidth
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update tracking properties
|
||||
this.lastIcon = this.icon;
|
||||
this.lastSize = this.size;
|
||||
this.lastColor = this.color;
|
||||
this.lastStrokeWidth = this.strokeWidth;
|
||||
|
||||
const container = this.shadowRoot?.querySelector('#iconContainer') as HTMLElement;
|
||||
if (!container || !this.icon) return;
|
||||
|
||||
// Clear container
|
||||
container.innerHTML = '';
|
||||
|
||||
// Create cache key
|
||||
const cacheKey = `${this.icon}:${this.size}:${this.color}:${this.strokeWidth}`;
|
||||
|
||||
// Check cache
|
||||
if (SioIcon.iconCache.has(cacheKey)) {
|
||||
container.innerHTML = SioIcon.iconCache.get(cacheKey)!;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Convert icon name to PascalCase (e.g., 'message-square' -> 'MessageSquare')
|
||||
const pascalCaseName = this.icon
|
||||
.split('-')
|
||||
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join('');
|
||||
|
||||
const iconComponent = (lucideIcons as any)[pascalCaseName];
|
||||
if (!iconComponent) {
|
||||
console.warn(`Lucide icon '${pascalCaseName}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the icon element
|
||||
const svgElement = createElement(iconComponent, {
|
||||
size: this.size,
|
||||
color: this.color,
|
||||
strokeWidth: this.strokeWidth,
|
||||
});
|
||||
|
||||
if (svgElement) {
|
||||
// Cache the result
|
||||
const svgString = svgElement.outerHTML;
|
||||
SioIcon.iconCache.set(cacheKey, svgString);
|
||||
|
||||
// Limit cache size
|
||||
if (SioIcon.iconCache.size > SioIcon.MAX_CACHE_SIZE) {
|
||||
const firstKey = SioIcon.iconCache.keys().next().value;
|
||||
SioIcon.iconCache.delete(firstKey);
|
||||
}
|
||||
|
||||
// Append to container
|
||||
container.appendChild(svgElement);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error rendering icon ${this.icon}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
public async disconnectedCallback() {
|
||||
await super.disconnectedCallback();
|
||||
// Clear references
|
||||
this.lastIcon = null;
|
||||
this.lastSize = null;
|
||||
this.lastColor = null;
|
||||
this.lastStrokeWidth = null;
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
|
||||
import * as sioInterfaces from '@social.io/interfaces';
|
||||
|
||||
@customElement('sio-subwidget-conversations')
|
||||
export class SioSubwidgetConversations extends DeesElement {
|
||||
// STATIC
|
||||
|
||||
// INSTANCE
|
||||
public conversations: sioInterfaces.ISioConversation[] = [
|
||||
{
|
||||
subject: 'Pricing page',
|
||||
parties: [
|
||||
{
|
||||
id: '1',
|
||||
description: 'Lossless Support',
|
||||
name: 'Lossless Support',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
description: 'you',
|
||||
name: 'you',
|
||||
},
|
||||
],
|
||||
conversationBlocks: [
|
||||
{
|
||||
partyId: '1',
|
||||
text: 'Hello there :) How can we help you?',
|
||||
},
|
||||
{
|
||||
partyId: '2',
|
||||
text: 'Hi! Where is your pricing page?',
|
||||
},
|
||||
],
|
||||
},{
|
||||
subject: 'Pricing page',
|
||||
parties: [
|
||||
{
|
||||
id: '1',
|
||||
description: 'Lossless Support',
|
||||
name: 'Lossless Support',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
description: 'you',
|
||||
name: 'you',
|
||||
},
|
||||
],
|
||||
conversationBlocks: [
|
||||
{
|
||||
partyId: '1',
|
||||
text: 'Hello there :) How can we help you?',
|
||||
},
|
||||
{
|
||||
partyId: '2',
|
||||
text: 'Hi! Where is your pricing page?',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
public static demo = () => html`<sio-subwidget-conversations></sio-subwidget-conversations>`;
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
${domtools.elementBasic.styles}
|
||||
<style>
|
||||
:host {
|
||||
color: ${this.goBright ? '#666' : '#ccc'};
|
||||
font-family: 'Dees Sans';
|
||||
}
|
||||
|
||||
.conversationbox {
|
||||
padding: 20px;
|
||||
transition: all 0.1s;
|
||||
min-height: 200px;
|
||||
margin: 20px;
|
||||
background: ${this.goBright ? '#fff' : '#111111'};
|
||||
border-radius: 16px;
|
||||
border-top: 1px solid rgba(250, 250, 250, 0.1);
|
||||
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.conversationbox .text {
|
||||
font-family: 'Dees Sans';
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.conversation {
|
||||
display: block;
|
||||
transition: all 0.1s;
|
||||
padding: 8px 0px 8px 0px;
|
||||
border-bottom: 1px solid;
|
||||
border-image: radial-gradient(rgba(136, 136, 136, 0.44), rgba(136, 136, 136, 0)) 1 / 1 / 0 stretch;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.conversation:last-of-type {
|
||||
border-bottom: none;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.conversation:hover {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.conversation:hover .gridcontainer {
|
||||
transform: translateX(2px)
|
||||
}
|
||||
|
||||
.conversation .gridcontainer {
|
||||
display: grid;
|
||||
grid-template-columns: 50px auto;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.conversation .gridcontainer .profilePicture {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50px;
|
||||
background: ${this.goBright ? '#EEE' : '#222'};
|
||||
}
|
||||
|
||||
.conversation .gridcontainer .text .topLine {
|
||||
font-family: 'Dees Sans';
|
||||
padding-top: 3px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.gridcontainer .gridcontainer .text .bottomLine {
|
||||
font-family: 'Dees Sans';
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
<div class="conversationbox">
|
||||
<div class="text">Your conversations:</div>
|
||||
|
||||
${this.conversations.map((conversationArg) => {
|
||||
return html`
|
||||
<div class="conversation">
|
||||
<div class="gridcontainer">
|
||||
<div class="profilePicture"></div>
|
||||
<div class="text">
|
||||
<div class="topLine">Today at 8:01</div>
|
||||
<div class="bottomLine">${conversationArg.subject}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
<dees-button>View more</dees-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
|
||||
import * as deesCatalog from '@design.estate/dees-catalog';
|
||||
deesCatalog;
|
||||
|
||||
@customElement('sio-subwidget-onboardme')
|
||||
export class SioSubwidgetOnboardme extends DeesElement {
|
||||
@property()
|
||||
public showCombox = false;
|
||||
|
||||
public static demo = () => html`
|
||||
<sio-subwidget-onboardme></sio-subwidget-onboardme>
|
||||
`;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
domtools.DomTools.setupDomTools();
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
${domtools.elementBasic.styles}
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
transition: all 0.1s;
|
||||
min-height: 200px;
|
||||
margin: 20px 20px 40px 20px;
|
||||
background: ${this.goBright ? '#fafafa' : '#111111'};
|
||||
border-radius: 16px;
|
||||
border-top: 1px solid rgba(250,250,250,0.1);
|
||||
box-shadow: 0px 0px 5px rgba(0,0,0,0.3);
|
||||
padding: 24px 24px 32px 24px;
|
||||
color: #CCC;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host(:hover) {
|
||||
}
|
||||
|
||||
.brandingbox {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
font-size: 10px;
|
||||
padding: 3px;
|
||||
border-top: 1px solid rgba(250,250,250, 0.1);
|
||||
font-family: 'Dees Code';
|
||||
background: ${this.goBright ? '#eee' : '#111111'};
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
Or search through our documentation
|
||||
<dees-input-text key="searchTerm" label="Search Term:"></dees-input-text>
|
||||
<dees-button>Search</dees-button>
|
||||
<div class="brandingbox">last updated: ${new Date().toISOString()}</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,45 @@
|
||||
import { html } from '@design.estate/dees-element';
|
||||
|
||||
export const mainpage = () => html` <lele-statusbar></lele-statusbar> `;
|
||||
export const mainpage = () => html`
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.demo-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.demo-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
}
|
||||
h2 {
|
||||
margin-bottom: 20px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.component-demo {
|
||||
margin: 20px 0;
|
||||
position: relative;
|
||||
min-height: 700px;
|
||||
}
|
||||
</style>
|
||||
<div class="demo-container">
|
||||
<div class="demo-section">
|
||||
<h2>Social.io Catalog Components</h2>
|
||||
|
||||
<div class="component-demo">
|
||||
<h3>FAB with Combox Demo</h3>
|
||||
<sio-fab .showCombox=${true}></sio-fab>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
Reference in New Issue
Block a user