Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55e8e192c9 | |||
| 286f6fd120 | |||
| 1401cd2c92 | |||
| 2323d1a01c | |||
| bbb6d09ecf | |||
| 2f54ee3f85 | |||
| a066e0de73 | |||
| 7731054f0e | |||
| 5f48ecf7af | |||
| f9d281c496 |
31
changelog.md
31
changelog.md
@@ -1,5 +1,36 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-12-17 - 1.4.1 - fix(ui)
|
||||||
|
handle on-screen keyboard visibility to adjust layout and prevent inputs from being obscured
|
||||||
|
|
||||||
|
- Add keyboard visibility state (isKeyboardVisible) and keyboardBlurTimeout in sio-combox.ts
|
||||||
|
- Listen for custom 'input-focus' and 'input-blur' events and toggle keyboard-visible host attribute
|
||||||
|
- Dispatch 'input-focus'/'input-blur' from sio-conversation-selector and sio-message-input on focus/blur
|
||||||
|
- Add connected/disconnected lifecycle handlers and updated() hook to manage attribute and cleanup timeouts
|
||||||
|
- Apply :host([keyboard-visible]) CSS to set height to 100vh / 100dvh when keyboard is visible
|
||||||
|
|
||||||
|
## 2025-12-17 - 1.4.0 - feat(elements)
|
||||||
|
update design tokens and sio-fab component; bump deps and update npmextra config
|
||||||
|
|
||||||
|
- Refactor color tokens to a neutral HSL palette (ts_web/elements/00colors.ts) and adjust focus ring token (ts_web/elements/00tokens.ts).
|
||||||
|
- Refactor sio-fab: move styles to static property, add responsive FAB sizing and getMobileIconSize(), bind icon sizes, manage host class ('combox-open'), and tidy lifecycle methods for better behavior and mobile support.
|
||||||
|
- Bump dependencies and devDependencies: @design.estate/dees-wcctools -> ^2.0.1, lucide -> ^0.561.0; @git.zone/tsbuild -> ^4.0.2, @git.zone/tsrun -> ^2.0.1, @git.zone/tswatch -> ^2.3.13, @types/node -> ^25.0.3, etc.
|
||||||
|
- Update npmextra.json: rename configuration keys (gitzone -> @git.zone/cli, npmci -> @ship.zone/szci) and add release.registries and accessLevel for publishing.
|
||||||
|
|
||||||
|
## 2025-12-08 - 1.3.0 - feat(components)
|
||||||
|
Add reusable message input component, refactor element properties to use accessor, update styles and docs, bump dependencies
|
||||||
|
|
||||||
|
- Add new <sio-message-input> component (auto-resizing textarea, file picker, send/files events) and integrate it into sio-conversation-view
|
||||||
|
- Refactor multiple element class fields to use the 'accessor' property pattern (sio-button, sio-combox, sio-conversation-view, sio-dropdown-menu, sio-fab, sio-icon, sio-image-lightbox, sio-pdf-viewer, sio-recorder, etc.)
|
||||||
|
- Significant visual and UX updates to sio-button (new secondary variant, sizing, spacing, icon sizing, focus/disabled behavior)
|
||||||
|
- Move inline conversation input logic into the new component and simplify message handling (dispatch send-message event with text and attachments)
|
||||||
|
- Improve PDF viewer: safer async pdf.js loader, resize observer for responsive rendering, better error fallback and lifecycle cleanup
|
||||||
|
- Enhance image lightbox (PDF handling via sio-pdf-viewer, zoom/drag controls, keyboard shortcuts, download/open actions)
|
||||||
|
- Add icon caching and make lucide upgrade (cache limit, PascalCase lookup) to reduce re-renders
|
||||||
|
- Polish tokens/styles: spacing, radii, shadows, transitions and small responsive/layout tweaks (e.g. main page padding)
|
||||||
|
- Update README to reflect the public package name, components, quick start and development instructions
|
||||||
|
- Bump runtime and dev dependencies (dees-element/domtools/wcctools, lucide, git.zone tools, push.rocks smartenv/types) and adjust tsconfig (target/module settings simplified)
|
||||||
|
|
||||||
## 2025-04-20 - 1.2.4 - fix(build)
|
## 2025-04-20 - 1.2.4 - fix(build)
|
||||||
Update build script and async function signature
|
Update build script and async function signature
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"gitzone": {
|
"@git.zone/cli": {
|
||||||
"projectType": "wcc",
|
"projectType": "wcc",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "gitlab.com",
|
"githost": "gitlab.com",
|
||||||
@@ -9,11 +9,16 @@
|
|||||||
"npmPackagename": "@social.io_private/catalog",
|
"npmPackagename": "@social.io_private/catalog",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"projectDomain": "social.io"
|
"projectDomain": "social.io"
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmci": {
|
"@ship.zone/szci": {
|
||||||
"npmRegistryUrl": "verdaccio.lossless.one",
|
"npmRegistryUrl": "verdaccio.lossless.one",
|
||||||
"npmGlobalTools": [],
|
"npmGlobalTools": []
|
||||||
"npmAccessLevel": "private"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
25
package.json
25
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@social.io/catalog",
|
"name": "@social.io/catalog",
|
||||||
"version": "1.2.5",
|
"version": "1.4.1",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "catalog for social.io",
|
"description": "catalog for social.io",
|
||||||
"main": "dist_ts_web/index.js",
|
"main": "dist_ts_web/index.js",
|
||||||
@@ -15,26 +15,25 @@
|
|||||||
"author": "Lossless GmbH",
|
"author": "Lossless GmbH",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@design.estate/dees-domtools": "^2.3.3",
|
"@design.estate/dees-domtools": "^2.3.6",
|
||||||
"@design.estate/dees-element": "^2.1.2",
|
"@design.estate/dees-element": "^2.1.3",
|
||||||
"@design.estate/dees-wcctools": "^1.1.1",
|
"@design.estate/dees-wcctools": "^2.0.1",
|
||||||
"@losslessone_private/loint-pubapi": "^1.0.14",
|
"@losslessone_private/loint-pubapi": "^1.0.14",
|
||||||
"@social.io/interfaces": "^1.2.1",
|
"@social.io/interfaces": "^1.2.1",
|
||||||
"lucide": "^0.525.0",
|
"lucide": "^0.561.0",
|
||||||
"rrweb": "2.0.0-alpha.4",
|
"rrweb": "2.0.0-alpha.4",
|
||||||
"rrweb-player": "1.0.0-alpha.4",
|
"rrweb-player": "1.0.0-alpha.4",
|
||||||
"rrweb-snapshot": "2.0.0-alpha.4"
|
"rrweb-snapshot": "2.0.0-alpha.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.6.4",
|
"@git.zone/tsbuild": "^4.0.2",
|
||||||
"@git.zone/tsbundle": "^2.5.1",
|
"@git.zone/tsbundle": "^2.6.3",
|
||||||
"@git.zone/tsrun": "^1.3.3",
|
"@git.zone/tsrun": "^2.0.1",
|
||||||
"@git.zone/tstest": "^2.3.1",
|
"@git.zone/tstest": "^3.1.3",
|
||||||
"@git.zone/tswatch": "^2.1.2",
|
"@git.zone/tswatch": "^2.3.13",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/smartenv": "^5.0.12",
|
"@push.rocks/smartenv": "^6.0.0",
|
||||||
"@push.rocks/tapbundle": "^6.0.3",
|
"@types/node": "^25.0.3"
|
||||||
"@types/node": "^22.14.1"
|
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
|
|||||||
8690
pnpm-lock.yaml
generated
8690
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
220
readme.md
220
readme.md
@@ -1,34 +1,204 @@
|
|||||||
# @social.io/private/catalog
|
# @social.io/catalog
|
||||||
|
|
||||||
the element catalog for the lossless organization
|
A modern, beautifully designed UI component library for building conversational interfaces and support chat experiences. Built with Lit Element and TypeScript.
|
||||||
|
|
||||||
## Availabililty and Links
|
## Issue Reporting and Security
|
||||||
|
|
||||||
- [npmjs.org (npm package)](https://www.npmjs.com/package/@social.io_private/catalog)
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||||
- [gitlab.com (source)](https://gitlab.com/social.io/private/catalog)
|
|
||||||
- [github.com (source mirror)](https://github.com/social.io/private/catalog)
|
|
||||||
- [docs (typedoc)](https://social.io/private.gitlab.io/catalog/)
|
|
||||||
|
|
||||||
## Status for master
|
## 🎯 Features
|
||||||
|
|
||||||
| Status Category | Status Badge |
|
- **Complete Chat UI** - Ready-to-use conversation components with message threads, typing indicators, and attachments
|
||||||
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
- **Floating Action Button** - Eye-catching FAB with smooth animations for triggering the chat interface
|
||||||
| GitLab Pipelines | [](https://lossless.cloud) |
|
- **PDF Viewer** - Built-in PDF rendering with zoom, pagination, and download capabilities
|
||||||
| GitLab Pipline Test Coverage | [](https://lossless.cloud) |
|
- **Image Lightbox** - Full-featured lightbox with zoom, pan, and keyboard navigation
|
||||||
| npm | [](https://lossless.cloud) |
|
- **Modern Design Tokens** - Consistent styling with customizable colors, spacing, typography, and shadows
|
||||||
| Snyk | [](https://lossless.cloud) |
|
- **Dark Mode Ready** - Full light/dark theme support out of the box
|
||||||
| TypeScript Support | [](https://lossless.cloud) |
|
- **Accessibility** - Keyboard navigation and proper ARIA attributes
|
||||||
| node Support | [](https://nodejs.org/dist/latest-v10.x/docs/api/) |
|
- **TypeScript First** - Full type definitions for all components
|
||||||
| Code Style | [](https://lossless.cloud) |
|
|
||||||
| PackagePhobia (total standalone install weight) | [](https://lossless.cloud) |
|
|
||||||
| PackagePhobia (package size on registry) | [](https://lossless.cloud) |
|
|
||||||
| BundlePhobia (total size when bundled) | [](https://lossless.cloud) |
|
|
||||||
|
|
||||||
## Usage
|
## 📦 Installation
|
||||||
|
|
||||||
For further information read the linked docs at the top of this readme.
|
```bash
|
||||||
|
npm install @social.io/catalog
|
||||||
|
# or
|
||||||
|
pnpm add @social.io/catalog
|
||||||
|
```
|
||||||
|
|
||||||
## Legal
|
## 🚀 Quick Start
|
||||||
|
|
||||||
> UNLICENSED licensed | **©** [Task Venture Capital GmbH](https://task.vc)
|
```typescript
|
||||||
> | By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
|
import { SioFab, SioCombox } from '@social.io/catalog';
|
||||||
|
|
||||||
|
// Components auto-register as custom elements
|
||||||
|
// Just use them in your HTML:
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Floating Action Button that opens the chat -->
|
||||||
|
<sio-fab></sio-fab>
|
||||||
|
|
||||||
|
<!-- Or use the full chat box directly -->
|
||||||
|
<sio-combox></sio-combox>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧩 Components
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
|
||||||
|
| Component | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `<sio-fab>` | Floating action button with animated chat icon |
|
||||||
|
| `<sio-combox>` | Complete chat interface with conversation list and message view |
|
||||||
|
| `<sio-button>` | Styled button with variants (primary, secondary, destructive, outline, ghost) |
|
||||||
|
| `<sio-icon>` | Lucide icon wrapper with size and color customization |
|
||||||
|
| `<sio-dropdown-menu>` | Animated dropdown menu with keyboard support |
|
||||||
|
|
||||||
|
### Conversation Components
|
||||||
|
|
||||||
|
| Component | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `<sio-conversation-selector>` | Searchable list of conversations with unread indicators |
|
||||||
|
| `<sio-conversation-view>` | Message thread with typing indicators and file attachments |
|
||||||
|
| `<sio-message-input>` | Auto-expanding textarea with file upload |
|
||||||
|
|
||||||
|
### Media Components
|
||||||
|
|
||||||
|
| Component | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `<sio-image-lightbox>` | Fullscreen image viewer with zoom and pan |
|
||||||
|
| `<sio-pdf-viewer>` | PDF renderer with page navigation and zoom controls |
|
||||||
|
|
||||||
|
### Utility Components
|
||||||
|
|
||||||
|
| Component | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `<sio-recorder>` | Session recording using rrweb |
|
||||||
|
|
||||||
|
## 💅 Styling & Theming
|
||||||
|
|
||||||
|
The library uses CSS custom properties for theming. The design system includes:
|
||||||
|
|
||||||
|
- **Colors** - Primary, secondary, accent, destructive, muted, and semantic colors
|
||||||
|
- **Typography** - System font stack with size and weight variants
|
||||||
|
- **Spacing** - Consistent spacing scale (0.5rem increments)
|
||||||
|
- **Radius** - Border radius tokens from sm to full
|
||||||
|
- **Shadows** - Elevation system from sm to 2xl
|
||||||
|
- **Transitions** - Smooth animation presets
|
||||||
|
|
||||||
|
### Dark Mode
|
||||||
|
|
||||||
|
Dark mode is automatically supported. The components use `bdTheme()` helper that switches between light and dark values:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { bdTheme } from '@social.io/catalog';
|
||||||
|
|
||||||
|
// Usage in styles
|
||||||
|
css`
|
||||||
|
background: ${bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 10%)')};
|
||||||
|
`
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📖 Usage Examples
|
||||||
|
|
||||||
|
### Basic Chat FAB
|
||||||
|
|
||||||
|
```html
|
||||||
|
<sio-fab></sio-fab>
|
||||||
|
```
|
||||||
|
|
||||||
|
The FAB opens a complete chat interface when clicked. It includes:
|
||||||
|
- Keyboard shortcut (Ctrl+S) to toggle
|
||||||
|
- Smooth scale and pulse animations
|
||||||
|
- Gradient background with glow effects
|
||||||
|
|
||||||
|
### Custom Button Variants
|
||||||
|
|
||||||
|
```html
|
||||||
|
<sio-button type="primary">Submit</sio-button>
|
||||||
|
<sio-button type="destructive">Delete</sio-button>
|
||||||
|
<sio-button type="outline">Cancel</sio-button>
|
||||||
|
<sio-button type="ghost" size="sm">
|
||||||
|
<sio-icon icon="settings"></sio-icon>
|
||||||
|
</sio-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Lightbox
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const lightbox = document.querySelector('sio-image-lightbox');
|
||||||
|
lightbox.open({
|
||||||
|
url: 'https://example.com/photo.jpg',
|
||||||
|
name: 'My Photo',
|
||||||
|
size: 1024000
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### PDF Viewer
|
||||||
|
|
||||||
|
```html
|
||||||
|
<sio-pdf-viewer
|
||||||
|
url="https://example.com/document.pdf"
|
||||||
|
fileName="document.pdf"
|
||||||
|
></sio-pdf-viewer>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dropdown Menu
|
||||||
|
|
||||||
|
```html
|
||||||
|
<sio-dropdown-menu
|
||||||
|
.items=${[
|
||||||
|
{ id: 'edit', label: 'Edit', icon: 'pencil' },
|
||||||
|
{ id: 'delete', label: 'Delete', icon: 'trash', destructive: true }
|
||||||
|
]}
|
||||||
|
@item-selected=${(e) => console.log('Selected:', e.detail.item)}
|
||||||
|
>
|
||||||
|
<sio-button type="ghost">
|
||||||
|
<sio-icon icon="more-vertical"></sio-icon>
|
||||||
|
</sio-button>
|
||||||
|
</sio-dropdown-menu>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Start development server with hot reload
|
||||||
|
pnpm watch
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
pnpm test
|
||||||
|
|
||||||
|
# Build for production
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Dependencies
|
||||||
|
|
||||||
|
- **@design.estate/dees-element** - Lit Element base with utilities
|
||||||
|
- **@design.estate/dees-domtools** - DOM manipulation helpers
|
||||||
|
- **lucide** - Beautiful open-source icons
|
||||||
|
- **rrweb** - Session recording/replay (for recorder component)
|
||||||
|
|
||||||
|
## License and Legal Information
|
||||||
|
|
||||||
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||||
|
|
||||||
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
### Trademarks
|
||||||
|
|
||||||
|
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
||||||
|
|
||||||
|
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||||
|
|
||||||
|
### Company Information
|
||||||
|
|
||||||
|
Task Venture Capital GmbH
|
||||||
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||||
|
|
||||||
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@social.io/catalog',
|
name: '@social.io/catalog',
|
||||||
version: '1.2.4',
|
version: '1.4.1',
|
||||||
description: 'catalog for social.io'
|
description: 'catalog for social.io'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,35 +4,35 @@ export const colors = {
|
|||||||
// Background colors - softer, more subtle
|
// Background colors - softer, more subtle
|
||||||
background: {
|
background: {
|
||||||
light: 'hsl(0 0% 100%)',
|
light: 'hsl(0 0% 100%)',
|
||||||
dark: 'hsl(224 71.4% 4.1%)'
|
dark: 'hsl(0 0% 4%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Foreground colors - less contrast for modern look
|
// Foreground colors - less contrast for modern look
|
||||||
foreground: {
|
foreground: {
|
||||||
light: 'hsl(224 71.4% 4.1%)',
|
light: 'hsl(0 0% 4%)',
|
||||||
dark: 'hsl(210 20% 98%)'
|
dark: 'hsl(0 0% 98%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Card colors - subtle elevation
|
// Card colors - subtle elevation
|
||||||
card: {
|
card: {
|
||||||
light: 'hsl(0 0% 100%)',
|
light: 'hsl(0 0% 100%)',
|
||||||
dark: 'hsl(224 71.4% 4.1%)'
|
dark: 'hsl(0 0% 4%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
cardForeground: {
|
cardForeground: {
|
||||||
light: 'hsl(224 71.4% 4.1%)',
|
light: 'hsl(0 0% 4%)',
|
||||||
dark: 'hsl(210 20% 98%)'
|
dark: 'hsl(0 0% 98%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Popover colors
|
// Popover colors
|
||||||
popover: {
|
popover: {
|
||||||
light: 'hsl(0 0% 100%)',
|
light: 'hsl(0 0% 100%)',
|
||||||
dark: 'hsl(222.2 84% 4.9%)'
|
dark: 'hsl(0 0% 5%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
popoverForeground: {
|
popoverForeground: {
|
||||||
light: 'hsl(222.2 84% 4.9%)',
|
light: 'hsl(0 0% 5%)',
|
||||||
dark: 'hsl(210 40% 98%)'
|
dark: 'hsl(0 0% 98%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Primary colors - modern indigo/blue
|
// Primary colors - modern indigo/blue
|
||||||
@@ -42,41 +42,41 @@ export const colors = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
primaryForeground: {
|
primaryForeground: {
|
||||||
light: 'hsl(210 20% 98%)',
|
light: 'hsl(0 0% 98%)',
|
||||||
dark: 'hsl(224 71.4% 4.1%)'
|
dark: 'hsl(0 0% 4%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Secondary colors - more subtle
|
// Secondary colors - more subtle
|
||||||
secondary: {
|
secondary: {
|
||||||
light: 'hsl(220 14.3% 95.9%)',
|
light: 'hsl(0 0% 96%)',
|
||||||
dark: 'hsl(215 27.9% 16.9%)'
|
dark: 'hsl(0 0% 17%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
secondaryForeground: {
|
secondaryForeground: {
|
||||||
light: 'hsl(220.9 39.3% 11%)',
|
light: 'hsl(0 0% 11%)',
|
||||||
dark: 'hsl(210 20% 98%)'
|
dark: 'hsl(0 0% 98%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Muted colors - softer grays
|
// Muted colors - softer grays
|
||||||
muted: {
|
muted: {
|
||||||
light: 'hsl(220 14.3% 95.9%)',
|
light: 'hsl(0 0% 96%)',
|
||||||
dark: 'hsl(215 27.9% 16.9%)'
|
dark: 'hsl(0 0% 17%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
mutedForeground: {
|
mutedForeground: {
|
||||||
light: 'hsl(220 8.9% 46.1%)',
|
light: 'hsl(0 0% 46%)',
|
||||||
dark: 'hsl(217.9 10.6% 64.9%)'
|
dark: 'hsl(0 0% 65%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Accent colors - subtle hover states
|
// Accent colors - subtle hover states
|
||||||
accent: {
|
accent: {
|
||||||
light: 'hsl(220 14.3% 95.9%)',
|
light: 'hsl(0 0% 96%)',
|
||||||
dark: 'hsl(215 27.9% 16.9%)'
|
dark: 'hsl(0 0% 17%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
accentForeground: {
|
accentForeground: {
|
||||||
light: 'hsl(220.9 39.3% 11%)',
|
light: 'hsl(0 0% 11%)',
|
||||||
dark: 'hsl(210 20% 98%)'
|
dark: 'hsl(0 0% 98%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Destructive colors - softer red
|
// Destructive colors - softer red
|
||||||
@@ -92,14 +92,14 @@ export const colors = {
|
|||||||
|
|
||||||
// Border color - very subtle
|
// Border color - very subtle
|
||||||
border: {
|
border: {
|
||||||
light: 'hsl(220 13% 91%)',
|
light: 'hsl(0 0% 91%)',
|
||||||
dark: 'hsl(215 27.9% 16.9%)'
|
dark: 'hsl(0 0% 17%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Input color
|
// Input color
|
||||||
input: {
|
input: {
|
||||||
light: 'hsl(214.3 31.8% 91.4%)',
|
light: 'hsl(0 0% 91%)',
|
||||||
dark: 'hsl(217.2 32.6% 17.5%)'
|
dark: 'hsl(0 0% 18%)'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Ring color - subtle focus indicator
|
// Ring color - subtle focus indicator
|
||||||
@@ -153,6 +153,12 @@ export const getColor = (colorName: keyof typeof colors, isDark: boolean = false
|
|||||||
};
|
};
|
||||||
|
|
||||||
// CSS helper for theme-aware colors
|
// CSS helper for theme-aware colors
|
||||||
export const bdTheme = (colorName: keyof typeof colors) => {
|
export const bdTheme = (colorNameOrLight: keyof typeof colors | string, dark?: string) => {
|
||||||
|
if (dark) {
|
||||||
|
// Direct color values provided
|
||||||
|
return cssManager.bdTheme(colorNameOrLight as string, dark);
|
||||||
|
}
|
||||||
|
// Color name from palette
|
||||||
|
const colorName = colorNameOrLight as keyof typeof colors;
|
||||||
return cssManager.bdTheme(colors[colorName].light, colors[colorName].dark);
|
return cssManager.bdTheme(colors[colorName].light, colors[colorName].dark);
|
||||||
};
|
};
|
||||||
@@ -185,7 +185,7 @@ export const focusRing = css`
|
|||||||
outline: 2px solid transparent;
|
outline: 2px solid transparent;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline-color: ${cssManager.bdTheme('hsl(222.2 84% 4.9%)', 'hsl(212.7 26.8% 83.9%)')};
|
outline-color: ${cssManager.bdTheme('hsl(0 0% 5%)', 'hsl(0 0% 84%)')};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export * from './sio-dropdown-menu.js';
|
|||||||
// Conversation components
|
// Conversation components
|
||||||
export * from './sio-conversation-selector.js';
|
export * from './sio-conversation-selector.js';
|
||||||
export * from './sio-conversation-view.js';
|
export * from './sio-conversation-view.js';
|
||||||
|
export * from './sio-message-input.js';
|
||||||
export * from './sio-combox.js';
|
export * from './sio-combox.js';
|
||||||
|
|
||||||
// Other components
|
// Other components
|
||||||
|
|||||||
@@ -36,19 +36,19 @@ export class SioButton extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public text: string = '';
|
public accessor text: string = '';
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public type: 'default' | 'primary' | 'destructive' | 'outline' | 'ghost' = 'default';
|
public accessor type: 'default' | 'primary' | 'secondary' | 'destructive' | 'outline' | 'ghost' = 'default';
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public size: 'sm' | 'default' | 'lg' = 'default';
|
public accessor size: 'sm' | 'default' | 'lg' = 'default';
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
public disabled: boolean = false;
|
public accessor disabled: boolean = false;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public status: 'normal' | 'pending' | 'success' | 'error' = 'normal';
|
public accessor status: 'normal' | 'pending' | 'success' | 'error' = 'normal';
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
@@ -68,117 +68,123 @@ export class SioButton extends DeesElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border-radius: ${unsafeCSS(radius.md)};
|
border-radius: 6px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: ${unsafeCSS(transitions.all)};
|
font-size: 14px;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
transition: all 120ms ease;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: 1px solid transparent;
|
border: none;
|
||||||
gap: ${unsafeCSS(spacing["2"])};
|
gap: 6px;
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Size variants */
|
/* Size variants */
|
||||||
.button.size-sm {
|
.button.size-sm {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
padding: 0 ${unsafeCSS(spacing["3"])};
|
padding: 0 12px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.size-default {
|
.button.size-default {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
padding: 0 ${unsafeCSS(spacing["4"])};
|
padding: 0 16px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.size-lg {
|
.button.size-lg {
|
||||||
height: 44px;
|
height: 42px;
|
||||||
padding: 0 ${unsafeCSS(spacing["6"])};
|
padding: 0 24px;
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Type variants */
|
/* Type variants */
|
||||||
.button.default {
|
.button.default {
|
||||||
background: ${bdTheme('background')};
|
background: ${bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 15%)')};
|
||||||
color: ${bdTheme('foreground')};
|
color: ${bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 95%)')};
|
||||||
border-color: ${bdTheme('border')};
|
|
||||||
box-shadow: ${unsafeCSS(shadows.sm)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.default:hover:not(.disabled) {
|
.button.default:hover:not(.disabled) {
|
||||||
background: ${bdTheme('accent')};
|
background: ${bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 20%)')};
|
||||||
border-color: ${bdTheme('accent')};
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: ${unsafeCSS(shadows.md)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.default:active:not(.disabled) {
|
.button.default:active:not(.disabled) {
|
||||||
transform: translateY(0);
|
background: ${bdTheme('hsl(0 0% 87%)', 'hsl(0 0% 18%)')};
|
||||||
box-shadow: ${unsafeCSS(shadows.sm)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.primary {
|
.button.primary {
|
||||||
background: ${bdTheme('primary')};
|
background: ${bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 95%)')};
|
||||||
color: ${bdTheme('primaryForeground')};
|
color: ${bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 0%)')};
|
||||||
box-shadow: ${unsafeCSS(shadows.sm)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.primary:hover:not(.disabled) {
|
.button.primary:hover:not(.disabled) {
|
||||||
opacity: 0.9;
|
background: ${bdTheme('hsl(0 0% 25%)', 'hsl(0 0% 100%)')};
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: ${unsafeCSS(shadows.md)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.primary:active:not(.disabled) {
|
.button.primary:active:not(.disabled) {
|
||||||
transform: translateY(0);
|
background: ${bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
|
||||||
box-shadow: ${unsafeCSS(shadows.sm)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Secondary variant */
|
||||||
|
.button.secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: ${bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 95%)')};
|
||||||
|
box-shadow: inset 0 0 0 1px ${bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 25%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.secondary:hover:not(.disabled) {
|
||||||
|
background: ${bdTheme('hsl(0 0% 96%)', 'hsl(0 0% 15%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.secondary:active:not(.disabled) {
|
||||||
|
background: ${bdTheme('hsl(0 0% 92%)', 'hsl(0 0% 12%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Destructive variant */
|
||||||
.button.destructive {
|
.button.destructive {
|
||||||
background: ${bdTheme('destructive')};
|
background: ${bdTheme('hsl(0 100% 95%)', 'hsl(0 50% 20%)')};
|
||||||
color: ${bdTheme('destructiveForeground')};
|
color: ${bdTheme('hsl(0 100% 45%)', 'hsl(0 100% 75%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.destructive:hover:not(.disabled) {
|
.button.destructive:hover:not(.disabled) {
|
||||||
opacity: 0.9;
|
background: ${bdTheme('hsl(0 100% 45%)', 'hsl(0 100% 50%)')};
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.destructive:active:not(.disabled) {
|
.button.destructive:active:not(.disabled) {
|
||||||
transform: translateY(1px);
|
background: ${bdTheme('hsl(0 100% 40%)', 'hsl(0 100% 45%)')};
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.outline {
|
.button.outline {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: ${bdTheme('foreground')};
|
color: ${bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 70%)')};
|
||||||
border-color: ${bdTheme('border')};
|
box-shadow: inset 0 0 0 1.5px ${bdTheme('hsl(0 0% 90%)', 'hsl(0 0% 30%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.outline:hover:not(.disabled) {
|
.button.outline:hover:not(.disabled) {
|
||||||
background: ${bdTheme('accent')};
|
color: ${bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 95%)')};
|
||||||
color: ${bdTheme('accentForeground')};
|
box-shadow: inset 0 0 0 1.5px ${bdTheme('hsl(0 0% 70%)', 'hsl(0 0% 50%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.outline:active:not(.disabled) {
|
.button.outline:active:not(.disabled) {
|
||||||
transform: translateY(1px);
|
background: ${bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 15%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.ghost {
|
.button.ghost {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: ${bdTheme('foreground')};
|
color: ${bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 70%)')};
|
||||||
border-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.ghost:hover:not(.disabled) {
|
.button.ghost:hover:not(.disabled) {
|
||||||
background: ${bdTheme('accent')};
|
color: ${bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 95%)')};
|
||||||
color: ${bdTheme('accentForeground')};
|
background: ${bdTheme('hsl(0 0% 0% / 0.05)', 'hsl(0 0% 100% / 0.05)')};
|
||||||
border-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.ghost:active:not(.disabled) {
|
.button.ghost:active:not(.disabled) {
|
||||||
background: ${bdTheme('accent')};
|
background: ${bdTheme('hsl(0 0% 0% / 0.1)', 'hsl(0 0% 100% / 0.1)')};
|
||||||
opacity: 0.8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Status states */
|
/* Status states */
|
||||||
@@ -214,13 +220,31 @@ export class SioButton extends DeesElement {
|
|||||||
.button.disabled {
|
.button.disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
transform: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Focus state */
|
/* Focus state */
|
||||||
.button:focus-visible {
|
.button:focus-visible {
|
||||||
outline: 2px solid ${bdTheme('ring')};
|
outline: 2px solid ${bdTheme('hsl(0 0% 15% / 0.2)', 'hsl(0 0% 95% / 0.2)')};
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Icon sizing within buttons */
|
||||||
|
.button sio-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.size-sm sio-icon {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.size-lg sio-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -260,6 +284,7 @@ export class SioButton extends DeesElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Let the native click bubble normally
|
// Let the native click bubble normally
|
||||||
// Don't dispatch a custom event to avoid double-triggering
|
// Don't dispatch a custom event to avoid double-triggering
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,13 +37,18 @@ export class SioCombox extends DeesElement {
|
|||||||
public static demo = () => html` <sio-combox></sio-combox> `;
|
public static demo = () => html` <sio-combox></sio-combox> `;
|
||||||
|
|
||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
public referenceObject: HTMLElement;
|
public accessor referenceObject: HTMLElement;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private selectedConversationId: string | null = null;
|
private accessor selectedConversationId: string | null = null;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private conversations: IConversation[] = [
|
private accessor isKeyboardVisible: boolean = false;
|
||||||
|
|
||||||
|
private keyboardBlurTimeout?: number;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private accessor conversations: IConversation[] = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
title: 'Technical Support',
|
title: 'Technical Support',
|
||||||
@@ -72,7 +77,7 @@ export class SioCombox extends DeesElement {
|
|||||||
];
|
];
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private messages: { [conversationId: string]: IMessage[] } = {
|
private accessor messages: { [conversationId: string]: IMessage[] } = {
|
||||||
'1': [
|
'1': [
|
||||||
{ id: '1', text: 'Hi, I\'m having trouble logging in', sender: 'user', time: '10:00 AM' },
|
{ 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: '2', text: 'I can help you with that. Can you tell me what error you\'re seeing?', sender: 'support', time: '10:02 AM' },
|
||||||
@@ -127,6 +132,50 @@ export class SioCombox extends DeesElement {
|
|||||||
domtools.DomTools.setupDomTools();
|
domtools.DomTools.setupDomTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async connectedCallback() {
|
||||||
|
await super.connectedCallback();
|
||||||
|
this.addEventListener('input-focus', this.handleInputFocus as EventListener);
|
||||||
|
this.addEventListener('input-blur', this.handleInputBlur as EventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnectedCallback() {
|
||||||
|
await super.disconnectedCallback();
|
||||||
|
this.removeEventListener('input-focus', this.handleInputFocus as EventListener);
|
||||||
|
this.removeEventListener('input-blur', this.handleInputBlur as EventListener);
|
||||||
|
if (this.keyboardBlurTimeout) {
|
||||||
|
clearTimeout(this.keyboardBlurTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputFocus = () => {
|
||||||
|
if (this.keyboardBlurTimeout) {
|
||||||
|
clearTimeout(this.keyboardBlurTimeout);
|
||||||
|
this.keyboardBlurTimeout = undefined;
|
||||||
|
}
|
||||||
|
this.isKeyboardVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputBlur = () => {
|
||||||
|
if (this.keyboardBlurTimeout) {
|
||||||
|
clearTimeout(this.keyboardBlurTimeout);
|
||||||
|
}
|
||||||
|
this.keyboardBlurTimeout = window.setTimeout(() => {
|
||||||
|
this.isKeyboardVisible = false;
|
||||||
|
this.keyboardBlurTimeout = undefined;
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
updated(changedProperties: Map<string, any>) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
if (changedProperties.has('isKeyboardVisible')) {
|
||||||
|
if (this.isKeyboardVisible) {
|
||||||
|
this.setAttribute('keyboard-visible', '');
|
||||||
|
} else {
|
||||||
|
this.removeAttribute('keyboard-visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
@@ -181,16 +230,31 @@ export class SioCombox extends DeesElement {
|
|||||||
border-radius: ${unsafeCSS(radius['2xl'])};
|
border-radius: ${unsafeCSS(radius['2xl'])};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive layout */
|
/* Desktop layout (default) */
|
||||||
@media (max-width: 600px) {
|
sio-conversation-selector {
|
||||||
|
width: 320px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sio-conversation-view {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
// Mobile responsive layout - full screen with sliding mechanics
|
||||||
|
cssManager.cssForPhablet(css`
|
||||||
:host {
|
:host {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host::before {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
sio-conversation-selector {
|
sio-conversation-selector {
|
||||||
@@ -227,19 +291,13 @@ export class SioCombox extends DeesElement {
|
|||||||
left: 0;
|
left: 0;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 601px) {
|
/* Keyboard visible adjustments */
|
||||||
sio-conversation-selector {
|
:host([keyboard-visible]) {
|
||||||
width: 320px;
|
height: 100vh;
|
||||||
flex-shrink: 0;
|
height: 100dvh;
|
||||||
}
|
}
|
||||||
|
`),
|
||||||
sio-conversation-view {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
|
|||||||
@@ -38,13 +38,13 @@ export class SioConversationSelector extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
@property({ type: Array })
|
@property({ type: Array })
|
||||||
public conversations: IConversation[] = [];
|
public accessor conversations: IConversation[] = [];
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public selectedConversationId: string | null = null;
|
public accessor selectedConversationId: string | null = null;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private searchQuery: string = '';
|
private accessor searchQuery: string = '';
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
@@ -64,11 +64,18 @@ export class SioConversationSelector extends DeesElement {
|
|||||||
background: ${bdTheme('background')};
|
background: ${bdTheme('background')};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-top {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: ${unsafeCSS(spacing["4"])};
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 0 0 ${unsafeCSS(spacing["4"])} 0;
|
margin: 0;
|
||||||
color: ${bdTheme('foreground')};
|
color: ${bdTheme('foreground')};
|
||||||
letter-spacing: -0.025em;
|
letter-spacing: -0.025em;
|
||||||
}
|
}
|
||||||
@@ -259,7 +266,17 @@ export class SioConversationSelector extends DeesElement {
|
|||||||
.conversation-list::-webkit-scrollbar-thumb:hover {
|
.conversation-list::-webkit-scrollbar-thumb:hover {
|
||||||
background: ${bdTheme('mutedForeground')};
|
background: ${bdTheme('mutedForeground')};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
|
// Mobile: show close button
|
||||||
|
cssManager.cssForPhablet(css`
|
||||||
|
.close-button {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
`),
|
||||||
];
|
];
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
@@ -270,7 +287,25 @@ export class SioConversationSelector extends DeesElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="header">
|
<div class="header">
|
||||||
|
<div class="header-top">
|
||||||
|
<sio-button
|
||||||
|
class="close-button"
|
||||||
|
type="ghost"
|
||||||
|
size="sm"
|
||||||
|
@click=${() => this.handleClose()}
|
||||||
|
>
|
||||||
|
<sio-icon icon="x" size="20"></sio-icon>
|
||||||
|
</sio-button>
|
||||||
<h2 class="title">Messages</h2>
|
<h2 class="title">Messages</h2>
|
||||||
|
<sio-button
|
||||||
|
type="primary"
|
||||||
|
size="sm"
|
||||||
|
@click=${() => this.startNewConversation()}
|
||||||
|
>
|
||||||
|
<sio-icon icon="plus" size="16"></sio-icon>
|
||||||
|
New
|
||||||
|
</sio-button>
|
||||||
|
</div>
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -278,6 +313,8 @@ export class SioConversationSelector extends DeesElement {
|
|||||||
placeholder="Search conversations..."
|
placeholder="Search conversations..."
|
||||||
.value=${this.searchQuery}
|
.value=${this.searchQuery}
|
||||||
@input=${(e: Event) => this.searchQuery = (e.target as HTMLInputElement).value}
|
@input=${(e: Event) => this.searchQuery = (e.target as HTMLInputElement).value}
|
||||||
|
@focus=${this.handleInputFocus}
|
||||||
|
@blur=${this.handleInputBlur}
|
||||||
/>
|
/>
|
||||||
<sio-icon class="search-icon" icon="search" size="16"></sio-icon>
|
<sio-icon class="search-icon" icon="search" size="16"></sio-icon>
|
||||||
</div>
|
</div>
|
||||||
@@ -321,4 +358,35 @@ export class SioConversationSelector extends DeesElement {
|
|||||||
composed: true
|
composed: true
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private startNewConversation() {
|
||||||
|
// Dispatch event for parent components to handle new conversation creation
|
||||||
|
this.dispatchEvent(new CustomEvent('new-conversation', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleClose() {
|
||||||
|
this.dispatchEvent(new CustomEvent('close', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputFocus() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.dispatchEvent(new CustomEvent('input-focus', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputBlur() {
|
||||||
|
this.dispatchEvent(new CustomEvent('input-blur', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -15,9 +15,11 @@ import { colors, bdTheme } from './00colors.js';
|
|||||||
import { spacing, radius, shadows, transitions } from './00tokens.js';
|
import { spacing, radius, shadows, transitions } from './00tokens.js';
|
||||||
import { fontFamilies, typography } from './00fonts.js';
|
import { fontFamilies, typography } from './00fonts.js';
|
||||||
import { SioDropdownMenu, type IDropdownMenuItem } from './sio-dropdown-menu.js';
|
import { SioDropdownMenu, type IDropdownMenuItem } from './sio-dropdown-menu.js';
|
||||||
|
import { SioMessageInput } from './sio-message-input.js';
|
||||||
|
|
||||||
// Make sure components are loaded
|
// Make sure components are loaded
|
||||||
SioDropdownMenu;
|
SioDropdownMenu;
|
||||||
|
SioMessageInput;
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
export interface IAttachment {
|
export interface IAttachment {
|
||||||
@@ -57,22 +59,19 @@ export class SioConversationView extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
public conversation: IConversationData | null = null;
|
public accessor conversation: IConversationData | null = null;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private messageText: string = '';
|
private accessor isTyping: boolean = false;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private isTyping: boolean = false;
|
private accessor isDragging: boolean = false;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private isDragging: boolean = false;
|
private accessor uploadingFiles: Map<string, { file: File; progress: number }> = new Map();
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private uploadingFiles: Map<string, { file: File; progress: number }> = new Map();
|
private accessor pendingAttachments: IAttachment[] = [];
|
||||||
|
|
||||||
@state()
|
|
||||||
private pendingAttachments: IAttachment[] = [];
|
|
||||||
|
|
||||||
private dropdownMenuItems: IDropdownMenuItem[] = [
|
private dropdownMenuItems: IDropdownMenuItem[] = [
|
||||||
{ id: 'mute', label: 'Mute notifications', icon: 'bell-off' },
|
{ id: 'mute', label: 'Mute notifications', icon: 'bell-off' },
|
||||||
@@ -238,45 +237,6 @@ export class SioConversationView extends DeesElement {
|
|||||||
-webkit-backdrop-filter: blur(10px);
|
-webkit-backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-wrapper {
|
|
||||||
display: flex;
|
|
||||||
gap: ${unsafeCSS(spacing["2"])};
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-input {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 42px;
|
|
||||||
max-height: 120px;
|
|
||||||
padding: ${unsafeCSS(spacing["2.5"])} ${unsafeCSS(spacing[3])};
|
|
||||||
background: ${bdTheme('secondary')};
|
|
||||||
border: 1px solid ${bdTheme('border')};
|
|
||||||
border-radius: ${unsafeCSS(radius.xl)};
|
|
||||||
font-size: 0.9375rem;
|
|
||||||
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')};
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-input:focus {
|
|
||||||
border-color: ${bdTheme('ring')};
|
|
||||||
background: ${bdTheme('background')};
|
|
||||||
box-shadow: 0 0 0 3px ${bdTheme('ring')}15;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: ${unsafeCSS(spacing["1"])};
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -440,7 +400,8 @@ export class SioConversationView extends DeesElement {
|
|||||||
.pending-attachments {
|
.pending-attachments {
|
||||||
padding: ${unsafeCSS(spacing["2"])} ${unsafeCSS(spacing["3"])};
|
padding: ${unsafeCSS(spacing["2"])} ${unsafeCSS(spacing["3"])};
|
||||||
background: ${bdTheme('secondary')};
|
background: ${bdTheme('secondary')};
|
||||||
border-radius: ${unsafeCSS(radius.md)};
|
border: 1px solid ${bdTheme('border')};
|
||||||
|
border-radius: ${unsafeCSS(radius.lg)};
|
||||||
margin-bottom: ${unsafeCSS(spacing["2"])};
|
margin-bottom: ${unsafeCSS(spacing["2"])};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,10 +441,6 @@ export class SioConversationView extends DeesElement {
|
|||||||
.remove-attachment:hover {
|
.remove-attachment:hover {
|
||||||
color: ${bdTheme('destructive')};
|
color: ${bdTheme('destructive')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -606,41 +563,10 @@ export class SioConversationView extends DeesElement {
|
|||||||
`)}
|
`)}
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
<div class="input-wrapper">
|
<sio-message-input
|
||||||
<textarea
|
@send-message=${this.handleMessageSend}
|
||||||
class="message-input"
|
@files-selected=${this.handleFilesSelected}
|
||||||
placeholder="Type a message..."
|
></sio-message-input>
|
||||||
.value=${this.messageText}
|
|
||||||
@input=${this.handleInput}
|
|
||||||
@keydown=${this.handleKeyDown}
|
|
||||||
rows="1"
|
|
||||||
></textarea>
|
|
||||||
<div class="input-actions">
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
class="file-input"
|
|
||||||
id="fileInput"
|
|
||||||
multiple
|
|
||||||
accept="image/*,.pdf,.doc,.docx,.txt"
|
|
||||||
@change=${this.handleFileSelect}
|
|
||||||
/>
|
|
||||||
<sio-button type="ghost" size="sm" @click=${(e: Event) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this.openFileSelector();
|
|
||||||
}}>
|
|
||||||
<sio-icon icon="paperclip" size="16"></sio-icon>
|
|
||||||
</sio-button>
|
|
||||||
<sio-button
|
|
||||||
type="primary"
|
|
||||||
size="sm"
|
|
||||||
?disabled=${!this.messageText.trim() && this.pendingAttachments.length === 0}
|
|
||||||
@click=${this.sendMessage}
|
|
||||||
>
|
|
||||||
<sio-icon icon="send" size="16"></sio-icon>
|
|
||||||
</sio-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -652,28 +578,14 @@ export class SioConversationView extends DeesElement {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleInput(e: Event) {
|
private handleMessageSend(event: CustomEvent) {
|
||||||
const textarea = e.target as HTMLTextAreaElement;
|
const { text, attachments } = event.detail;
|
||||||
this.messageText = textarea.value;
|
|
||||||
|
|
||||||
// Auto-resize textarea
|
if (!text.trim() && attachments.length === 0) return;
|
||||||
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() && this.pendingAttachments.length === 0) return;
|
|
||||||
|
|
||||||
const message: IMessage = {
|
const message: IMessage = {
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
text: this.messageText.trim(),
|
text: text.trim(),
|
||||||
sender: 'user',
|
sender: 'user',
|
||||||
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
||||||
status: 'sending',
|
status: 'sending',
|
||||||
@@ -687,13 +599,8 @@ export class SioConversationView extends DeesElement {
|
|||||||
composed: true
|
composed: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Clear input and attachments
|
// Clear pending attachments
|
||||||
this.messageText = '';
|
|
||||||
this.pendingAttachments = [];
|
this.pendingAttachments = [];
|
||||||
const textarea = this.shadowRoot?.querySelector('.message-input') as HTMLTextAreaElement;
|
|
||||||
if (textarea) {
|
|
||||||
textarea.style.height = 'auto';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simulate typing indicator (remove in production)
|
// Simulate typing indicator (remove in production)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -704,6 +611,13 @@ export class SioConversationView extends DeesElement {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleFilesSelected(event: CustomEvent) {
|
||||||
|
const { files } = event.detail;
|
||||||
|
// Handle files if needed
|
||||||
|
// For now, we're handling attachments separately in the parent component
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public updated() {
|
public updated() {
|
||||||
// Scroll to bottom when new messages arrive
|
// Scroll to bottom when new messages arrive
|
||||||
const container = this.shadowRoot?.querySelector('#messages');
|
const container = this.shadowRoot?.querySelector('#messages');
|
||||||
|
|||||||
@@ -49,13 +49,13 @@ export class SioDropdownMenu extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
@property({ type: Array })
|
@property({ type: Array })
|
||||||
public items: IDropdownMenuItem[] = [];
|
public accessor items: IDropdownMenuItem[] = [];
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public align: 'left' | 'right' = 'right';
|
public accessor align: 'left' | 'right' = 'right';
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private isOpen: boolean = false;
|
private accessor isOpen: boolean = false;
|
||||||
|
|
||||||
private documentClickHandler: (e: MouseEvent) => void;
|
private documentClickHandler: (e: MouseEvent) => void;
|
||||||
private scrollHandler: () => void;
|
private scrollHandler: () => void;
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import {
|
|||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
cssManager,
|
cssManager,
|
||||||
css,
|
css,
|
||||||
|
unsafeCSS,
|
||||||
|
state,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
|
|
||||||
import { SioCombox } from './sio-combox.js';
|
import { SioCombox } from './sio-combox.js';
|
||||||
import { SioIcon } from './sio-icon.js';
|
import { SioIcon } from './sio-icon.js';
|
||||||
import { state } from '@design.estate/dees-element';
|
|
||||||
SioCombox;
|
SioCombox;
|
||||||
SioIcon;
|
SioIcon;
|
||||||
|
|
||||||
@@ -29,13 +30,13 @@ declare global {
|
|||||||
@customElement('sio-fab')
|
@customElement('sio-fab')
|
||||||
export class SioFab extends DeesElement {
|
export class SioFab extends DeesElement {
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
public showCombox = false;
|
public accessor showCombox = false;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private hasShownOnce = false;
|
private accessor hasShownOnce = false;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private shouldPulse = false;
|
private accessor shouldPulse = false;
|
||||||
|
|
||||||
public static demo = () => html` <sio-fab .showCombox=${true}></sio-fab> `;
|
public static demo = () => html` <sio-fab .showCombox=${true}></sio-fab> `;
|
||||||
|
|
||||||
@@ -44,10 +45,9 @@ export class SioFab extends DeesElement {
|
|||||||
domtools.DomTools.setupDomTools();
|
domtools.DomTools.setupDomTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public static styles = [
|
||||||
return html`
|
cssManager.defaultStyles,
|
||||||
${domtools.elementBasic.styles}
|
css`
|
||||||
<style>
|
|
||||||
:host {
|
:host {
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -61,27 +61,28 @@ export class SioFab extends DeesElement {
|
|||||||
--fab-gradient-end: #a855f7;
|
--fab-gradient-end: #a855f7;
|
||||||
--fab-gradient-hover-end: #c026d3;
|
--fab-gradient-hover-end: #c026d3;
|
||||||
--fab-shadow-color: rgba(139, 92, 246, 0.25);
|
--fab-shadow-color: rgba(139, 92, 246, 0.25);
|
||||||
|
--fab-size: 60px;
|
||||||
|
--fab-combox-offset: calc(var(--fab-size) + ${unsafeCSS(spacing["4"])});
|
||||||
}
|
}
|
||||||
|
|
||||||
#mainbox {
|
#mainbox {
|
||||||
transition: ${transitions.all};
|
transition: ${unsafeCSS(transitions.all)};
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
height: 60px;
|
height: var(--fab-size);
|
||||||
width: 60px;
|
width: var(--fab-size);
|
||||||
box-shadow: 0 4px 16px -2px rgba(0, 0, 0, 0.1), 0 2px 8px -2px rgba(0, 0, 0, 0.06);
|
box-shadow: 0 4px 16px -2px rgba(0, 0, 0, 0.1), 0 2px 8px -2px rgba(0, 0, 0, 0.06);
|
||||||
line-height: 60px;
|
line-height: var(--fab-size);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: linear-gradient(135deg, var(--fab-gradient-start) 0%, var(--fab-gradient-mid) 50%, var(--fab-gradient-end) 100%);
|
background: linear-gradient(135deg, var(--fab-gradient-start) 0%, var(--fab-gradient-mid) 50%, var(--fab-gradient-end) 100%);
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: ${radius.full};
|
border-radius: ${unsafeCSS(radius.full)};
|
||||||
user-select: none;
|
user-select: none;
|
||||||
border: none;
|
border: none;
|
||||||
animation: fabEntrance 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
animation: fabEntrance 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
-webkit-backdrop-filter: blur(10px);
|
-webkit-backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
@@ -135,9 +136,6 @@ export class SioFab extends DeesElement {
|
|||||||
#mainbox:hover {
|
#mainbox:hover {
|
||||||
transform: scale(1.02);
|
transform: scale(1.02);
|
||||||
background: linear-gradient(135deg, var(--fab-gradient-start) 0%, var(--fab-gradient-mid) 50%, var(--fab-gradient-hover-end) 100%);
|
background: linear-gradient(135deg, var(--fab-gradient-start) 0%, var(--fab-gradient-mid) 50%, var(--fab-gradient-hover-end) 100%);
|
||||||
}
|
|
||||||
|
|
||||||
#mainbox:hover {
|
|
||||||
box-shadow: 0 8px 20px -4px var(--fab-shadow-color);
|
box-shadow: 0 8px 20px -4px var(--fab-shadow-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,15 +172,25 @@ export class SioFab extends DeesElement {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#mainbox .icon.open {
|
#mainbox .icon.open {
|
||||||
opacity: ${this.showCombox ? '0' : '1'};
|
opacity: 1;
|
||||||
transform: ${this.showCombox ? 'rotate(45deg) scale(0.9)' : 'rotate(0deg) scale(1)'};
|
transform: rotate(0deg) scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#mainbox .icon.close {
|
#mainbox .icon.close {
|
||||||
opacity: ${this.showCombox ? '1' : '0'};
|
opacity: 0;
|
||||||
transform: ${this.showCombox ? 'rotate(0deg) scale(1)' : 'rotate(-45deg) scale(0.9)'};
|
transform: rotate(-45deg) scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When combox is open */
|
||||||
|
:host(.combox-open) #mainbox .icon.open {
|
||||||
|
opacity: 0;
|
||||||
|
transform: rotate(45deg) scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.combox-open) #mainbox .icon.close {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(0deg) scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#mainbox .icon sio-icon {
|
#mainbox .icon sio-icon {
|
||||||
@@ -203,11 +211,11 @@ export class SioFab extends DeesElement {
|
|||||||
|
|
||||||
#comboxContainer sio-combox {
|
#comboxContainer sio-combox {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: calc(60px + ${spacing["4"]});
|
bottom: var(--fab-combox-offset);
|
||||||
right: 0;
|
right: 0;
|
||||||
transition: ${transitions.all};
|
transition: ${unsafeCSS(transitions.all)};
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
transform: translateY(${spacing["5"]});
|
transform: translateY(${unsafeCSS(spacing["5"])});
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -221,17 +229,51 @@ export class SioFab extends DeesElement {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
</style>
|
`,
|
||||||
|
// Mobile responsive styles - smaller FAB and full-screen combox
|
||||||
|
cssManager.cssForPhablet(css`
|
||||||
|
:host {
|
||||||
|
--fab-size: 48px;
|
||||||
|
bottom: 16px;
|
||||||
|
right: 16px;
|
||||||
|
will-change: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comboxContainer {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: auto;
|
||||||
|
right: auto;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comboxContainer sio-combox {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comboxContainer.show sio-combox {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
];
|
||||||
|
|
||||||
|
public render(): TemplateResult {
|
||||||
|
return html`
|
||||||
<div id="mainbox"
|
<div id="mainbox"
|
||||||
class="${this.shouldPulse ? 'pulse' : ''}"
|
class="${this.shouldPulse ? 'pulse' : ''}"
|
||||||
@click=${this.toggleCombox}
|
@click=${this.toggleCombox}
|
||||||
@animationend=${() => { this.shouldPulse = false; }}
|
@animationend=${() => { this.shouldPulse = false; }}
|
||||||
>
|
>
|
||||||
<div class="icon open">
|
<div class="icon open">
|
||||||
<sio-icon icon="message-square" size="28"></sio-icon>
|
<sio-icon icon="message-square" size="24"></sio-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="icon close">
|
<div class="icon close">
|
||||||
<sio-icon icon="x" size="22"></sio-icon>
|
<sio-icon icon="x" size="20"></sio-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="comboxContainer" class="${this.showCombox ? 'show' : ''}">
|
<div id="comboxContainer" class="${this.showCombox ? 'show' : ''}">
|
||||||
@@ -270,7 +312,16 @@ export class SioFab extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async updated(changedProperties: Map<string | number | symbol, unknown>) {
|
public async updated(changedProperties: Map<string | number | symbol, unknown>) {
|
||||||
await super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
// Update host class based on combox state
|
||||||
|
if (changedProperties.has('showCombox')) {
|
||||||
|
if (this.showCombox) {
|
||||||
|
this.classList.add('combox-open');
|
||||||
|
} else {
|
||||||
|
this.classList.remove('combox-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set reference object when combox is rendered
|
// Set reference object when combox is rendered
|
||||||
if ((changedProperties.has('showCombox') || changedProperties.has('hasShownOnce')) &&
|
if ((changedProperties.has('showCombox') || changedProperties.has('hasShownOnce')) &&
|
||||||
|
|||||||
@@ -29,16 +29,16 @@ export class SioIcon extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public icon: string;
|
public accessor icon: string;
|
||||||
|
|
||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public size: number = 24;
|
public accessor size: number = 24;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public color: string = 'currentColor';
|
public accessor color: string = 'currentColor';
|
||||||
|
|
||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public strokeWidth: number = 2;
|
public accessor strokeWidth: number = 2;
|
||||||
|
|
||||||
// Cache for rendered icons
|
// Cache for rendered icons
|
||||||
private static iconCache = new Map<string, string>();
|
private static iconCache = new Map<string, string>();
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ export class SioImageLightbox extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
public isOpen: boolean = false;
|
public accessor isOpen: boolean = false;
|
||||||
|
|
||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
public file: ILightboxFile | null = null;
|
public accessor file: ILightboxFile | null = null;
|
||||||
|
|
||||||
// For backwards compatibility
|
// For backwards compatibility
|
||||||
public get image(): ILightboxFile | null {
|
public get image(): ILightboxFile | null {
|
||||||
@@ -60,16 +60,16 @@ export class SioImageLightbox extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private fileLoaded: boolean = false;
|
private accessor fileLoaded: boolean = false;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private scale: number = 1;
|
private accessor scale: number = 1;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private translateX: number = 0;
|
private accessor translateX: number = 0;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private translateY: number = 0;
|
private accessor translateY: number = 0;
|
||||||
|
|
||||||
private isDragging: boolean = false;
|
private isDragging: boolean = false;
|
||||||
private startX: number = 0;
|
private startX: number = 0;
|
||||||
|
|||||||
297
ts_web/elements/sio-message-input.ts
Normal file
297
ts_web/elements/sio-message-input.ts
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'sio-message-input': SioMessageInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('sio-message-input')
|
||||||
|
export class SioMessageInput extends DeesElement {
|
||||||
|
public static demo = () => html`
|
||||||
|
<sio-message-input style="width: 600px;"></sio-message-input>
|
||||||
|
`;
|
||||||
|
|
||||||
|
@property({ type: String })
|
||||||
|
public accessor placeholder: string = 'Type a message...';
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public accessor disabled: boolean = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private accessor messageText: string = '';
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private accessor pendingAttachments: File[] = [];
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
font-family: ${unsafeCSS(fontFamilies.sans)};
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
background: ${bdTheme('secondary')};
|
||||||
|
border: 1px solid ${bdTheme('border')};
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 2px;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container:focus-within {
|
||||||
|
border-color: ${bdTheme('ring')};
|
||||||
|
background: ${bdTheme('background')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 44px;
|
||||||
|
max-height: 120px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
font-size: 15px;
|
||||||
|
color: ${bdTheme('foreground')};
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
font-family: ${unsafeCSS(fontFamilies.sans)};
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input::placeholder {
|
||||||
|
color: ${bdTheme('mutedForeground')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: ${bdTheme('mutedForeground')};
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 120ms ease;
|
||||||
|
position: relative;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:hover {
|
||||||
|
color: ${bdTheme('foreground')};
|
||||||
|
background: ${bdTheme('hsl(0 0% 0% / 0.05)', 'hsl(0 0% 100% / 0.05)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.attachment {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.attachment:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.send {
|
||||||
|
background: ${bdTheme('primary')};
|
||||||
|
color: white;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.send:hover {
|
||||||
|
background: ${bdTheme('hsl(221 83% 49%)', 'hsl(217 91% 65%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.send:disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.send:disabled:hover {
|
||||||
|
background: ${bdTheme('primary')};
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon styles */
|
||||||
|
sio-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.send sio-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
public render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div class="input-container">
|
||||||
|
<textarea
|
||||||
|
class="message-input"
|
||||||
|
placeholder="${this.placeholder}"
|
||||||
|
.value=${this.messageText}
|
||||||
|
@input=${this.handleInput}
|
||||||
|
@keydown=${this.handleKeyDown}
|
||||||
|
@focus=${this.handleFocus}
|
||||||
|
@blur=${this.handleBlur}
|
||||||
|
?disabled=${this.disabled}
|
||||||
|
rows="1"
|
||||||
|
></textarea>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
class="file-input"
|
||||||
|
id="fileInput"
|
||||||
|
multiple
|
||||||
|
accept="image/*,.pdf,.doc,.docx,.txt"
|
||||||
|
@change=${this.handleFileSelect}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="action-button attachment"
|
||||||
|
@click=${this.openFileSelector}
|
||||||
|
?disabled=${this.disabled}
|
||||||
|
>
|
||||||
|
<sio-icon icon="paperclip"></sio-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="action-button send"
|
||||||
|
?disabled=${!this.messageText.trim() || this.disabled}
|
||||||
|
@click=${this.sendMessage}
|
||||||
|
>
|
||||||
|
<sio-icon icon="send"></sio-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInput(event: Event) {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement;
|
||||||
|
this.messageText = textarea.value;
|
||||||
|
|
||||||
|
// Auto-resize textarea
|
||||||
|
textarea.style.height = 'auto';
|
||||||
|
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleKeyDown(event: KeyboardEvent) {
|
||||||
|
if (event.key === 'Enter' && !event.shiftKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.sendMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleFocus() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.dispatchEvent(new CustomEvent('input-focus', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleBlur() {
|
||||||
|
this.dispatchEvent(new CustomEvent('input-blur', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendMessage() {
|
||||||
|
if (!this.messageText.trim() && this.pendingAttachments.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispatchEvent(new CustomEvent('send-message', {
|
||||||
|
detail: {
|
||||||
|
text: this.messageText,
|
||||||
|
attachments: this.pendingAttachments
|
||||||
|
},
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Clear input
|
||||||
|
this.messageText = '';
|
||||||
|
this.pendingAttachments = [];
|
||||||
|
|
||||||
|
// Reset textarea height
|
||||||
|
const textarea = this.shadowRoot?.querySelector('.message-input') as HTMLTextAreaElement;
|
||||||
|
if (textarea) {
|
||||||
|
textarea.style.height = 'auto';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private openFileSelector() {
|
||||||
|
const fileInput = this.shadowRoot?.querySelector('#fileInput') as HTMLInputElement;
|
||||||
|
if (fileInput) {
|
||||||
|
fileInput.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleFileSelect(event: Event) {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
const files = Array.from(input.files || []);
|
||||||
|
|
||||||
|
this.pendingAttachments = [...this.pendingAttachments, ...files];
|
||||||
|
|
||||||
|
this.dispatchEvent(new CustomEvent('files-selected', {
|
||||||
|
detail: { files },
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
input.value = ''; // Clear input for re-selection
|
||||||
|
}
|
||||||
|
|
||||||
|
public focus() {
|
||||||
|
const textarea = this.shadowRoot?.querySelector('.message-input') as HTMLTextAreaElement;
|
||||||
|
textarea?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear() {
|
||||||
|
this.messageText = '';
|
||||||
|
this.pendingAttachments = [];
|
||||||
|
const textarea = this.shadowRoot?.querySelector('.message-input') as HTMLTextAreaElement;
|
||||||
|
if (textarea) {
|
||||||
|
textarea.style.height = 'auto';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,8 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
// Import design tokens
|
// Import design tokens
|
||||||
import { colors, bdTheme } from './00colors.js';
|
import { bdTheme } from './00colors.js';
|
||||||
import { spacing, radius, shadows, transitions } from './00tokens.js';
|
import { spacing, radius, shadows } from './00tokens.js';
|
||||||
import { fontFamilies } from './00fonts.js';
|
import { fontFamilies } from './00fonts.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -36,28 +36,28 @@ export class SioPdfViewer extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public url: string = '';
|
public accessor url: string = '';
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public fileName: string = 'document.pdf';
|
public accessor fileName: string = 'document.pdf';
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private isLoading: boolean = true;
|
private accessor isLoading: boolean = true;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private hasError: boolean = false;
|
private accessor hasError: boolean = false;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private pdfDocument: any = null;
|
private accessor pdfDocument: any = null;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private currentPage: number = 1;
|
private accessor currentPage: number = 1;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private totalPages: number = 0;
|
private accessor totalPages: number = 0;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private scale: number = 1;
|
private accessor scale: number = 1;
|
||||||
|
|
||||||
private static pdfJsLoaded: boolean = false;
|
private static pdfJsLoaded: boolean = false;
|
||||||
private static pdfJsLoading: Promise<void> | null = null;
|
private static pdfJsLoading: Promise<void> | null = null;
|
||||||
@@ -398,8 +398,8 @@ export class SioPdfViewer extends DeesElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async firstUpdated() {
|
public async firstUpdated(_changedProperties: any) {
|
||||||
await super.firstUpdated();
|
super.firstUpdated(_changedProperties);
|
||||||
|
|
||||||
// Set up resize observer for responsive rendering after first render
|
// Set up resize observer for responsive rendering after first render
|
||||||
const container = this.shadowRoot?.querySelector('.pdf-container');
|
const container = this.shadowRoot?.querySelector('.pdf-container');
|
||||||
@@ -414,7 +414,7 @@ export class SioPdfViewer extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async updated(changedProperties: Map<string | number | symbol, unknown>) {
|
public async updated(changedProperties: Map<string | number | symbol, unknown>) {
|
||||||
await super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (changedProperties.has('url') && this.url) {
|
if (changedProperties.has('url') && this.url) {
|
||||||
await this.loadPdf();
|
await this.loadPdf();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export class SioRecorder extends DeesElement {
|
|||||||
* Query for the div in our template that will be used for playback.
|
* Query for the div in our template that will be used for playback.
|
||||||
*/
|
*/
|
||||||
@query('#playback')
|
@query('#playback')
|
||||||
private playbackDiv!: HTMLDivElement;
|
private accessor playbackDiv!: HTMLDivElement;
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
:host {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const mainpage = () => html`
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 20px;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.demo-section {
|
.demo-section {
|
||||||
background: white;
|
background: white;
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"experimentalDecorators": true,
|
|
||||||
"useDefineForClassFields": false,
|
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {}
|
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"dist_*/**/*.d.ts"
|
"dist_*/**/*.d.ts"
|
||||||
|
|||||||
Reference in New Issue
Block a user