liqgui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +190 -0
- package/dist/components/glass-accordion.d.ts +15 -0
- package/dist/components/glass-accordion.js +173 -0
- package/dist/components/glass-avatar.d.ts +9 -0
- package/dist/components/glass-avatar.js +98 -0
- package/dist/components/glass-badge.d.ts +10 -0
- package/dist/components/glass-badge.js +151 -0
- package/dist/components/glass-button.d.ts +6 -0
- package/dist/components/glass-button.js +124 -0
- package/dist/components/glass-card.d.ts +8 -0
- package/dist/components/glass-card.js +102 -0
- package/dist/components/glass-dropdown.d.ts +12 -0
- package/dist/components/glass-dropdown.js +182 -0
- package/dist/components/glass-input.d.ts +8 -0
- package/dist/components/glass-input.js +151 -0
- package/dist/components/glass-modal.d.ts +11 -0
- package/dist/components/glass-modal.js +128 -0
- package/dist/components/glass-navbar.d.ts +6 -0
- package/dist/components/glass-navbar.js +84 -0
- package/dist/components/glass-progress.d.ts +12 -0
- package/dist/components/glass-progress.js +159 -0
- package/dist/components/glass-slider.d.ts +17 -0
- package/dist/components/glass-slider.js +168 -0
- package/dist/components/glass-tabs.d.ts +7 -0
- package/dist/components/glass-tabs.js +102 -0
- package/dist/components/glass-toast.d.ts +8 -0
- package/dist/components/glass-toast.js +128 -0
- package/dist/components/glass-toggle.d.ts +9 -0
- package/dist/components/glass-toggle.js +112 -0
- package/dist/components/glass-tooltip.d.ts +14 -0
- package/dist/components/glass-tooltip.js +214 -0
- package/dist/core/base-element.d.ts +4 -0
- package/dist/core/base-element.js +9 -0
- package/dist/core/curves.d.ts +22 -0
- package/dist/core/curves.js +32 -0
- package/dist/core/focus-trap.d.ts +1 -0
- package/dist/core/focus-trap.js +19 -0
- package/dist/core/glow.d.ts +3 -0
- package/dist/core/glow.js +57 -0
- package/dist/core/motion.d.ts +12 -0
- package/dist/core/motion.js +54 -0
- package/dist/core/spring-engine.d.ts +12 -0
- package/dist/core/spring-engine.js +90 -0
- package/dist/core/supports.d.ts +1 -0
- package/dist/core/supports.js +1 -0
- package/dist/core/theme.d.ts +2 -0
- package/dist/core/theme.js +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +40 -0
- package/package.json +47 -0
- package/src/styles/tokens.css +140 -0
package/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# liqgui ✨
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/liqgui)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Glassmorphism UI components that actually feel good. Built with spring physics.
|
|
7
|
+
|
|
8
|
+
**[📖 Docs & Demo](https://bymehul.github.io/liqgui/docs/index.html)**
|
|
9
|
+
|
|
10
|
+
## Why this exists
|
|
11
|
+
|
|
12
|
+
Most glass-effect libraries look nice but feel off—stiff transitions, janky hovers, no thought put into motion. liqgui uses real spring physics (not just `ease-in-out`) so everything moves like it has weight.
|
|
13
|
+
|
|
14
|
+
## What you get
|
|
15
|
+
|
|
16
|
+
- **15 components** — buttons, cards, modals, toasts, the works
|
|
17
|
+
- **Spring animations** — physics-based, not CSS keyframe guesswork
|
|
18
|
+
- **3D effects** — tilt on hover, ripples, glow trails
|
|
19
|
+
- **Dark/light themes** — one CSS variable swap
|
|
20
|
+
- **Accessible** — keyboard nav, focus traps, ARIA done right
|
|
21
|
+
- **No dependencies** — just TypeScript, works with anything
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install liqgui
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<link rel="stylesheet" href="node_modules/liqgui/src/styles/tokens.css">
|
|
33
|
+
<script type="module">
|
|
34
|
+
import 'liqgui';
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<glass-button variant="primary">Click Me</glass-button>
|
|
38
|
+
<glass-card glow>Your content here</glass-card>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Components
|
|
42
|
+
|
|
43
|
+
### Buttons & Actions
|
|
44
|
+
```html
|
|
45
|
+
<glass-button>Default</glass-button>
|
|
46
|
+
<glass-button variant="primary">Primary</glass-button>
|
|
47
|
+
<glass-button variant="outline">Outline</glass-button>
|
|
48
|
+
<glass-button variant="ghost">Ghost</glass-button>
|
|
49
|
+
<glass-button loading>Loading...</glass-button>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Form Controls
|
|
53
|
+
```html
|
|
54
|
+
<glass-input placeholder="Enter text"></glass-input>
|
|
55
|
+
<glass-toggle checked>Enable feature</glass-toggle>
|
|
56
|
+
<glass-slider value="50" min="0" max="100"></glass-slider>
|
|
57
|
+
<glass-dropdown>
|
|
58
|
+
<span slot="trigger">Select option</span>
|
|
59
|
+
<button role="option" data-value="1">Option 1</button>
|
|
60
|
+
<button role="option" data-value="2">Option 2</button>
|
|
61
|
+
</glass-dropdown>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Layout & Content
|
|
65
|
+
```html
|
|
66
|
+
<glass-card glow>Card with glow effect</glass-card>
|
|
67
|
+
<glass-card no-tilt>Card without 3D tilt</glass-card>
|
|
68
|
+
|
|
69
|
+
<glass-tabs>
|
|
70
|
+
<button data-value="tab1">Tab 1</button>
|
|
71
|
+
<button data-value="tab2">Tab 2</button>
|
|
72
|
+
</glass-tabs>
|
|
73
|
+
|
|
74
|
+
<glass-navbar auto-hide>
|
|
75
|
+
<span slot="brand">Brand</span>
|
|
76
|
+
<a href="#">Link</a>
|
|
77
|
+
<glass-button slot="actions">CTA</glass-button>
|
|
78
|
+
</glass-navbar>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Overlays
|
|
82
|
+
```html
|
|
83
|
+
<glass-modal id="modal">Modal content</glass-modal>
|
|
84
|
+
<script>document.getElementById('modal').open();</script>
|
|
85
|
+
|
|
86
|
+
<glass-tooltip position="top">
|
|
87
|
+
<button>Hover me</button>
|
|
88
|
+
<span slot="content">Tooltip text</span>
|
|
89
|
+
</glass-tooltip>
|
|
90
|
+
|
|
91
|
+
<glass-popover position="bottom">
|
|
92
|
+
<button slot="trigger">Click me</button>
|
|
93
|
+
<div>Popover content</div>
|
|
94
|
+
</glass-popover>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Notifications
|
|
98
|
+
```javascript
|
|
99
|
+
import { GlassToast } from 'liqgui';
|
|
100
|
+
GlassToast.show('Success!', 'success');
|
|
101
|
+
GlassToast.show('Error!', 'error');
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Data Display
|
|
105
|
+
```html
|
|
106
|
+
<glass-avatar size="lg" status="online">AB</glass-avatar>
|
|
107
|
+
<glass-avatar-group>
|
|
108
|
+
<glass-avatar>A</glass-avatar>
|
|
109
|
+
<glass-avatar>B</glass-avatar>
|
|
110
|
+
</glass-avatar-group>
|
|
111
|
+
|
|
112
|
+
<glass-badge variant="success">New</glass-badge>
|
|
113
|
+
<glass-badge-wrapper count="5">
|
|
114
|
+
<button>Notifications</button>
|
|
115
|
+
</glass-badge-wrapper>
|
|
116
|
+
|
|
117
|
+
<glass-progress value="75" max="100">75%</glass-progress>
|
|
118
|
+
<glass-progress variant="circular" value="50">50%</glass-progress>
|
|
119
|
+
<glass-progress indeterminate>Loading</glass-progress>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Accordion
|
|
123
|
+
```html
|
|
124
|
+
<glass-accordion>
|
|
125
|
+
<glass-accordion-item open>
|
|
126
|
+
<span slot="title">Question 1</span>
|
|
127
|
+
Answer content here.
|
|
128
|
+
</glass-accordion-item>
|
|
129
|
+
<glass-accordion-item>
|
|
130
|
+
<span slot="title">Question 2</span>
|
|
131
|
+
More content.
|
|
132
|
+
</glass-accordion-item>
|
|
133
|
+
</glass-accordion>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Core Utilities
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
import {
|
|
140
|
+
springAnimate, bouncySpring,
|
|
141
|
+
motion,
|
|
142
|
+
setTheme
|
|
143
|
+
} from 'liqgui';
|
|
144
|
+
|
|
145
|
+
// Spring animation
|
|
146
|
+
springAnimate(0, 100, v => el.style.left = `${v}px`, bouncySpring);
|
|
147
|
+
|
|
148
|
+
// Motion library
|
|
149
|
+
motion.fadeIn(element);
|
|
150
|
+
motion.scaleIn(element);
|
|
151
|
+
motion.slideInUp(element);
|
|
152
|
+
|
|
153
|
+
// Theming
|
|
154
|
+
setTheme('dark');
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## CSS Custom Properties
|
|
158
|
+
|
|
159
|
+
```css
|
|
160
|
+
:root {
|
|
161
|
+
--lg-blur: 20px;
|
|
162
|
+
--lg-radius: 20px;
|
|
163
|
+
--lg-accent: var(--lg-accent-blue);
|
|
164
|
+
--lg-bg: var(--lg-bg-dark);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Component List
|
|
169
|
+
|
|
170
|
+
| Category | Components |
|
|
171
|
+
|----------|-----------|
|
|
172
|
+
| Actions | `glass-button` |
|
|
173
|
+
| Forms | `glass-input`, `glass-toggle`, `glass-slider`, `glass-dropdown` |
|
|
174
|
+
| Layout | `glass-card`, `glass-tabs`, `glass-navbar`, `glass-accordion` |
|
|
175
|
+
| Overlays | `glass-modal`, `glass-tooltip`, `glass-popover`, `glass-toast` |
|
|
176
|
+
| Data | `glass-avatar`, `glass-badge`, `glass-progress` |
|
|
177
|
+
|
|
178
|
+
## Browser Support
|
|
179
|
+
|
|
180
|
+
Modern browsers only (Chrome, Firefox, Safari, Edge). Uses Web Components and `backdrop-filter`.
|
|
181
|
+
|
|
182
|
+
## Support
|
|
183
|
+
|
|
184
|
+
If you find this useful, consider supporting my work:
|
|
185
|
+
|
|
186
|
+
[](https://www.patreon.com/c/bymehul) [](https://www.paywithchai.in/bymehul)
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT — do whatever you want with it.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseElement } from "../core/base-element.js";
|
|
2
|
+
export declare class GlassAccordion extends BaseElement {
|
|
3
|
+
static get observedAttributes(): string[];
|
|
4
|
+
connectedCallback(): void;
|
|
5
|
+
}
|
|
6
|
+
export declare class GlassAccordionItem extends BaseElement {
|
|
7
|
+
private contentEl?;
|
|
8
|
+
private contentHeight;
|
|
9
|
+
static get observedAttributes(): string[];
|
|
10
|
+
connectedCallback(): void;
|
|
11
|
+
toggle(): void;
|
|
12
|
+
open(): void;
|
|
13
|
+
close(): void;
|
|
14
|
+
attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { BaseElement } from "../core/base-element.js";
|
|
2
|
+
import { springAnimate, gentleSpring } from "../core/spring-engine.js";
|
|
3
|
+
export class GlassAccordion extends BaseElement {
|
|
4
|
+
static get observedAttributes() {
|
|
5
|
+
return ["multiple"];
|
|
6
|
+
}
|
|
7
|
+
connectedCallback() {
|
|
8
|
+
this.mount(`
|
|
9
|
+
<div class="accordion">
|
|
10
|
+
<slot></slot>
|
|
11
|
+
</div>
|
|
12
|
+
`, `
|
|
13
|
+
:host {
|
|
14
|
+
display: block;
|
|
15
|
+
}
|
|
16
|
+
.accordion {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: 0.5rem;
|
|
20
|
+
}
|
|
21
|
+
`);
|
|
22
|
+
// Handle item toggle
|
|
23
|
+
this.addEventListener("accordion-toggle", ((e) => {
|
|
24
|
+
if (!this.hasAttribute("multiple")) {
|
|
25
|
+
// Close other items
|
|
26
|
+
this.querySelectorAll("glass-accordion-item[open]").forEach(item => {
|
|
27
|
+
if (item !== e.target) {
|
|
28
|
+
item.removeAttribute("open");
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
customElements.define("glass-accordion", GlassAccordion);
|
|
36
|
+
export class GlassAccordionItem extends BaseElement {
|
|
37
|
+
constructor() {
|
|
38
|
+
super(...arguments);
|
|
39
|
+
this.contentHeight = 0;
|
|
40
|
+
}
|
|
41
|
+
static get observedAttributes() {
|
|
42
|
+
return ["open"];
|
|
43
|
+
}
|
|
44
|
+
connectedCallback() {
|
|
45
|
+
var _a;
|
|
46
|
+
this.mount(`
|
|
47
|
+
<div class="item">
|
|
48
|
+
<button class="header" aria-expanded="false">
|
|
49
|
+
<span class="title"><slot name="title"></slot></span>
|
|
50
|
+
<svg class="icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
51
|
+
<path d="M6 9l6 6 6-6"/>
|
|
52
|
+
</svg>
|
|
53
|
+
</button>
|
|
54
|
+
<div class="content-wrapper">
|
|
55
|
+
<div class="content">
|
|
56
|
+
<slot></slot>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
`, `
|
|
61
|
+
:host {
|
|
62
|
+
display: block;
|
|
63
|
+
}
|
|
64
|
+
.item {
|
|
65
|
+
background: var(--lg-bg);
|
|
66
|
+
backdrop-filter: blur(var(--lg-blur));
|
|
67
|
+
border: 1px solid var(--lg-border);
|
|
68
|
+
border-radius: var(--lg-radius);
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
}
|
|
71
|
+
.header {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: space-between;
|
|
75
|
+
width: 100%;
|
|
76
|
+
padding: 1rem 1.25rem;
|
|
77
|
+
background: transparent;
|
|
78
|
+
border: none;
|
|
79
|
+
color: inherit;
|
|
80
|
+
font: inherit;
|
|
81
|
+
font-weight: 500;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
text-align: left;
|
|
84
|
+
transition: background 0.2s ease;
|
|
85
|
+
}
|
|
86
|
+
.header:hover {
|
|
87
|
+
background: rgba(255, 255, 255, 0.05);
|
|
88
|
+
}
|
|
89
|
+
.header:focus-visible {
|
|
90
|
+
outline: 2px solid var(--lg-accent-focus);
|
|
91
|
+
outline-offset: -2px;
|
|
92
|
+
}
|
|
93
|
+
.icon {
|
|
94
|
+
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
95
|
+
flex-shrink: 0;
|
|
96
|
+
opacity: 0.7;
|
|
97
|
+
}
|
|
98
|
+
:host([open]) .icon {
|
|
99
|
+
transform: rotate(180deg);
|
|
100
|
+
}
|
|
101
|
+
.content-wrapper {
|
|
102
|
+
height: 0;
|
|
103
|
+
overflow: hidden;
|
|
104
|
+
}
|
|
105
|
+
.content {
|
|
106
|
+
padding: 0 1.25rem 1rem;
|
|
107
|
+
opacity: 0.8;
|
|
108
|
+
line-height: 1.6;
|
|
109
|
+
}
|
|
110
|
+
`);
|
|
111
|
+
this.contentEl = this.root.querySelector(".content-wrapper");
|
|
112
|
+
// Get content height
|
|
113
|
+
requestAnimationFrame(() => {
|
|
114
|
+
const content = this.root.querySelector(".content");
|
|
115
|
+
if (content) {
|
|
116
|
+
this.contentHeight = content.offsetHeight;
|
|
117
|
+
}
|
|
118
|
+
if (this.hasAttribute("open")) {
|
|
119
|
+
this.contentEl.style.height = `${this.contentHeight}px`;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
// Toggle on header click
|
|
123
|
+
(_a = this.root.querySelector(".header")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", () => {
|
|
124
|
+
this.toggle();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
toggle() {
|
|
128
|
+
if (this.hasAttribute("open")) {
|
|
129
|
+
this.close();
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
this.open();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
open() {
|
|
136
|
+
var _a;
|
|
137
|
+
this.setAttribute("open", "");
|
|
138
|
+
(_a = this.root.querySelector(".header")) === null || _a === void 0 ? void 0 : _a.setAttribute("aria-expanded", "true");
|
|
139
|
+
// Dispatch event for accordion parent
|
|
140
|
+
this.dispatchEvent(new CustomEvent("accordion-toggle", { bubbles: true }));
|
|
141
|
+
// Animate open
|
|
142
|
+
if (this.contentEl) {
|
|
143
|
+
const content = this.root.querySelector(".content");
|
|
144
|
+
this.contentHeight = (content === null || content === void 0 ? void 0 : content.offsetHeight) || 0;
|
|
145
|
+
springAnimate(0, this.contentHeight, (v) => {
|
|
146
|
+
this.contentEl.style.height = `${v}px`;
|
|
147
|
+
}, gentleSpring);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
close() {
|
|
151
|
+
var _a;
|
|
152
|
+
this.removeAttribute("open");
|
|
153
|
+
(_a = this.root.querySelector(".header")) === null || _a === void 0 ? void 0 : _a.setAttribute("aria-expanded", "false");
|
|
154
|
+
// Animate close
|
|
155
|
+
if (this.contentEl) {
|
|
156
|
+
const currentHeight = this.contentEl.offsetHeight;
|
|
157
|
+
springAnimate(currentHeight, 0, (v) => {
|
|
158
|
+
this.contentEl.style.height = `${v}px`;
|
|
159
|
+
}, gentleSpring);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
163
|
+
if (name === "open" && this.contentEl) {
|
|
164
|
+
if (newValue !== null && oldValue === null) {
|
|
165
|
+
// Opening
|
|
166
|
+
}
|
|
167
|
+
else if (newValue === null && oldValue !== null) {
|
|
168
|
+
// Closing handled in close()
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
customElements.define("glass-accordion-item", GlassAccordionItem);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseElement } from "../core/base-element.js";
|
|
2
|
+
export declare class GlassAvatar extends BaseElement {
|
|
3
|
+
static get observedAttributes(): string[];
|
|
4
|
+
connectedCallback(): void;
|
|
5
|
+
attributeChangedCallback(): void;
|
|
6
|
+
}
|
|
7
|
+
export declare class GlassAvatarGroup extends BaseElement {
|
|
8
|
+
connectedCallback(): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { BaseElement } from "../core/base-element.js";
|
|
2
|
+
export class GlassAvatar extends BaseElement {
|
|
3
|
+
static get observedAttributes() {
|
|
4
|
+
return ["src", "alt", "size", "status"];
|
|
5
|
+
}
|
|
6
|
+
connectedCallback() {
|
|
7
|
+
const src = this.getAttribute("src");
|
|
8
|
+
const alt = this.getAttribute("alt") || "";
|
|
9
|
+
const size = this.getAttribute("size") || "md";
|
|
10
|
+
const status = this.getAttribute("status");
|
|
11
|
+
this.mount(`
|
|
12
|
+
<div class="avatar ${size}">
|
|
13
|
+
${src
|
|
14
|
+
? `<img src="${src}" alt="${alt}" />`
|
|
15
|
+
: `<span class="initials"><slot></slot></span>`}
|
|
16
|
+
${status ? `<span class="status ${status}"></span>` : ""}
|
|
17
|
+
</div>
|
|
18
|
+
`, `
|
|
19
|
+
:host {
|
|
20
|
+
display: inline-block;
|
|
21
|
+
}
|
|
22
|
+
.avatar {
|
|
23
|
+
position: relative;
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
border-radius: 50%;
|
|
28
|
+
background: var(--lg-bg);
|
|
29
|
+
backdrop-filter: blur(var(--lg-blur));
|
|
30
|
+
border: 2px solid var(--lg-border);
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Sizes */
|
|
35
|
+
.avatar.xs { width: 24px; height: 24px; font-size: 0.625rem; }
|
|
36
|
+
.avatar.sm { width: 32px; height: 32px; font-size: 0.75rem; }
|
|
37
|
+
.avatar.md { width: 40px; height: 40px; font-size: 0.875rem; }
|
|
38
|
+
.avatar.lg { width: 56px; height: 56px; font-size: 1.125rem; }
|
|
39
|
+
.avatar.xl { width: 80px; height: 80px; font-size: 1.5rem; }
|
|
40
|
+
|
|
41
|
+
img {
|
|
42
|
+
width: 100%;
|
|
43
|
+
height: 100%;
|
|
44
|
+
object-fit: cover;
|
|
45
|
+
}
|
|
46
|
+
.initials {
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
text-transform: uppercase;
|
|
49
|
+
color: rgba(255, 255, 255, 0.9);
|
|
50
|
+
}
|
|
51
|
+
.status {
|
|
52
|
+
position: absolute;
|
|
53
|
+
bottom: 0;
|
|
54
|
+
right: 0;
|
|
55
|
+
width: 25%;
|
|
56
|
+
height: 25%;
|
|
57
|
+
min-width: 8px;
|
|
58
|
+
min-height: 8px;
|
|
59
|
+
border-radius: 50%;
|
|
60
|
+
border: 2px solid rgba(18, 18, 22, 0.9);
|
|
61
|
+
}
|
|
62
|
+
.status.online { background: #30d158; }
|
|
63
|
+
.status.offline { background: #8e8e93; }
|
|
64
|
+
.status.busy { background: #ff453a; }
|
|
65
|
+
.status.away { background: #ffd60a; }
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
attributeChangedCallback() {
|
|
69
|
+
// Re-render on attribute changes
|
|
70
|
+
if (this.isConnected) {
|
|
71
|
+
this.connectedCallback();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
customElements.define("glass-avatar", GlassAvatar);
|
|
76
|
+
// Avatar group for stacking
|
|
77
|
+
export class GlassAvatarGroup extends BaseElement {
|
|
78
|
+
connectedCallback() {
|
|
79
|
+
this.mount(`<slot></slot>`, `
|
|
80
|
+
:host {
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
flex-direction: row-reverse;
|
|
83
|
+
}
|
|
84
|
+
::slotted(glass-avatar) {
|
|
85
|
+
margin-left: -0.75rem;
|
|
86
|
+
transition: transform 0.2s ease;
|
|
87
|
+
}
|
|
88
|
+
::slotted(glass-avatar:hover) {
|
|
89
|
+
transform: translateY(-4px);
|
|
90
|
+
z-index: 1;
|
|
91
|
+
}
|
|
92
|
+
::slotted(glass-avatar:last-child) {
|
|
93
|
+
margin-left: 0;
|
|
94
|
+
}
|
|
95
|
+
`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
customElements.define("glass-avatar-group", GlassAvatarGroup);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseElement } from "../core/base-element.js";
|
|
2
|
+
export declare class GlassBadge extends BaseElement {
|
|
3
|
+
static get observedAttributes(): string[];
|
|
4
|
+
connectedCallback(): void;
|
|
5
|
+
}
|
|
6
|
+
export declare class GlassBadgeWrapper extends BaseElement {
|
|
7
|
+
static get observedAttributes(): string[];
|
|
8
|
+
connectedCallback(): void;
|
|
9
|
+
attributeChangedCallback(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { BaseElement } from "../core/base-element.js";
|
|
2
|
+
export class GlassBadge extends BaseElement {
|
|
3
|
+
static get observedAttributes() {
|
|
4
|
+
return ["variant", "size", "dot"];
|
|
5
|
+
}
|
|
6
|
+
connectedCallback() {
|
|
7
|
+
const variant = this.getAttribute("variant") || "default";
|
|
8
|
+
const size = this.getAttribute("size") || "md";
|
|
9
|
+
const isDot = this.hasAttribute("dot");
|
|
10
|
+
this.mount(`
|
|
11
|
+
<span class="badge ${variant} ${size} ${isDot ? 'dot' : ''}">
|
|
12
|
+
${isDot ? '' : '<slot></slot>'}
|
|
13
|
+
</span>
|
|
14
|
+
`, `
|
|
15
|
+
:host {
|
|
16
|
+
display: inline-block;
|
|
17
|
+
}
|
|
18
|
+
.badge {
|
|
19
|
+
display: inline-flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
padding: 0.25rem 0.6rem;
|
|
23
|
+
border-radius: 999px;
|
|
24
|
+
font-size: 0.75rem;
|
|
25
|
+
font-weight: 600;
|
|
26
|
+
background: var(--lg-bg);
|
|
27
|
+
backdrop-filter: blur(var(--lg-blur));
|
|
28
|
+
border: 1px solid var(--lg-border);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Sizes */
|
|
32
|
+
.badge.sm { padding: 0.125rem 0.4rem; font-size: 0.625rem; }
|
|
33
|
+
.badge.md { padding: 0.25rem 0.6rem; font-size: 0.75rem; }
|
|
34
|
+
.badge.lg { padding: 0.375rem 0.75rem; font-size: 0.875rem; }
|
|
35
|
+
|
|
36
|
+
/* Dot mode */
|
|
37
|
+
.badge.dot {
|
|
38
|
+
width: 8px;
|
|
39
|
+
height: 8px;
|
|
40
|
+
padding: 0;
|
|
41
|
+
min-width: 8px;
|
|
42
|
+
}
|
|
43
|
+
.badge.dot.sm { width: 6px; height: 6px; }
|
|
44
|
+
.badge.dot.lg { width: 10px; height: 10px; }
|
|
45
|
+
|
|
46
|
+
/* Variants */
|
|
47
|
+
.badge.default {
|
|
48
|
+
background: rgba(255, 255, 255, 0.15);
|
|
49
|
+
}
|
|
50
|
+
.badge.primary {
|
|
51
|
+
background: var(--lg-accent);
|
|
52
|
+
border-color: transparent;
|
|
53
|
+
color: white;
|
|
54
|
+
}
|
|
55
|
+
.badge.success {
|
|
56
|
+
background: rgba(48, 209, 88, 0.2);
|
|
57
|
+
border-color: rgba(48, 209, 88, 0.4);
|
|
58
|
+
color: #30d158;
|
|
59
|
+
}
|
|
60
|
+
.badge.warning {
|
|
61
|
+
background: rgba(255, 214, 10, 0.2);
|
|
62
|
+
border-color: rgba(255, 214, 10, 0.4);
|
|
63
|
+
color: #ffd60a;
|
|
64
|
+
}
|
|
65
|
+
.badge.error {
|
|
66
|
+
background: rgba(255, 69, 58, 0.2);
|
|
67
|
+
border-color: rgba(255, 69, 58, 0.4);
|
|
68
|
+
color: #ff453a;
|
|
69
|
+
}
|
|
70
|
+
.badge.info {
|
|
71
|
+
background: rgba(90, 200, 250, 0.2);
|
|
72
|
+
border-color: rgba(90, 200, 250, 0.4);
|
|
73
|
+
color: #5ac8fa;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Dot variants */
|
|
77
|
+
.badge.dot.success { background: #30d158; }
|
|
78
|
+
.badge.dot.warning { background: #ffd60a; }
|
|
79
|
+
.badge.dot.error { background: #ff453a; }
|
|
80
|
+
.badge.dot.info { background: #5ac8fa; }
|
|
81
|
+
.badge.dot.primary { background: #007aff; }
|
|
82
|
+
`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
customElements.define("glass-badge", GlassBadge);
|
|
86
|
+
// Badge wrapper for adding badge to other elements
|
|
87
|
+
export class GlassBadgeWrapper extends BaseElement {
|
|
88
|
+
static get observedAttributes() {
|
|
89
|
+
return ["count", "variant", "max", "dot"];
|
|
90
|
+
}
|
|
91
|
+
connectedCallback() {
|
|
92
|
+
const count = parseInt(this.getAttribute("count") || "0");
|
|
93
|
+
const max = parseInt(this.getAttribute("max") || "99");
|
|
94
|
+
const variant = this.getAttribute("variant") || "error";
|
|
95
|
+
const isDot = this.hasAttribute("dot");
|
|
96
|
+
const displayCount = count > max ? `${max}+` : String(count);
|
|
97
|
+
this.mount(`
|
|
98
|
+
<div class="wrapper">
|
|
99
|
+
<slot></slot>
|
|
100
|
+
${(count > 0 || isDot) ? `
|
|
101
|
+
<span class="badge ${variant} ${isDot ? 'dot' : ''}">
|
|
102
|
+
${isDot ? '' : displayCount}
|
|
103
|
+
</span>
|
|
104
|
+
` : ''}
|
|
105
|
+
</div>
|
|
106
|
+
`, `
|
|
107
|
+
:host {
|
|
108
|
+
display: inline-block;
|
|
109
|
+
}
|
|
110
|
+
.wrapper {
|
|
111
|
+
position: relative;
|
|
112
|
+
display: inline-block;
|
|
113
|
+
}
|
|
114
|
+
.badge {
|
|
115
|
+
position: absolute;
|
|
116
|
+
top: -4px;
|
|
117
|
+
right: -4px;
|
|
118
|
+
min-width: 18px;
|
|
119
|
+
height: 18px;
|
|
120
|
+
padding: 0 5px;
|
|
121
|
+
border-radius: 999px;
|
|
122
|
+
font-size: 0.625rem;
|
|
123
|
+
font-weight: 700;
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
justify-content: center;
|
|
127
|
+
border: 2px solid rgba(18, 18, 22, 0.9);
|
|
128
|
+
z-index: 1;
|
|
129
|
+
}
|
|
130
|
+
.badge.dot {
|
|
131
|
+
width: 10px;
|
|
132
|
+
height: 10px;
|
|
133
|
+
min-width: 10px;
|
|
134
|
+
padding: 0;
|
|
135
|
+
top: -2px;
|
|
136
|
+
right: -2px;
|
|
137
|
+
}
|
|
138
|
+
.badge.error { background: #ff453a; color: white; }
|
|
139
|
+
.badge.success { background: #30d158; color: white; }
|
|
140
|
+
.badge.warning { background: #ffd60a; color: black; }
|
|
141
|
+
.badge.info { background: #5ac8fa; color: white; }
|
|
142
|
+
.badge.primary { background: #007aff; color: white; }
|
|
143
|
+
`);
|
|
144
|
+
}
|
|
145
|
+
attributeChangedCallback() {
|
|
146
|
+
if (this.isConnected) {
|
|
147
|
+
this.connectedCallback();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
customElements.define("glass-badge-wrapper", GlassBadgeWrapper);
|