@nectary/labs 2.3.4 → 2.4.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 +354 -0
- package/color-select/index.d.ts +39 -0
- package/color-select/index.js +96 -0
- package/index.d.ts +2 -0
- package/index.js +3 -0
- package/package.json +2 -6
- package/{phone-preview.d.ts → phone-preview/index.d.ts} +18 -7
- package/phone-preview/index.js +94 -0
- package/phone-preview-rcs-channel/index.d.ts +49 -0
- package/phone-preview-rcs-channel/index.js +175 -0
- package/phone-preview-rcs-channel-actions/index.d.ts +37 -0
- package/phone-preview-rcs-channel-actions/index.js +126 -0
- package/phone-preview-rcs-channel-info/index.d.ts +29 -0
- package/phone-preview-rcs-channel-info/index.js +64 -0
- package/phone-preview-rcs-channel-info-option/index.d.ts +40 -0
- package/phone-preview-rcs-channel-info-option/index.js +106 -0
- package/phone-preview-rcs-channel-options/index.d.ts +27 -0
- package/phone-preview-rcs-channel-options/index.js +46 -0
- package/phone-preview-rcs-channel-tabs/index.d.ts +34 -0
- package/phone-preview-rcs-channel-tabs/index.js +79 -0
- package/{phone-preview-rcs-chat.d.ts → phone-preview-rcs-chat/index.d.ts} +20 -8
- package/phone-preview-rcs-chat/index.js +79 -0
- package/phone-preview-rcs-chat-message/index.d.ts +35 -0
- package/phone-preview-rcs-chat-message/index.js +48 -0
- package/utils/element.d.ts +9 -0
- package/utils/element.js +35 -0
- package/utils/index.d.ts +1 -1
- package/utils/index.js +1 -1
- package/Readme.md +0 -1
- package/color-select.d.ts +0 -34
- package/color-select.js +0 -79
- package/phone-preview-rcs-channel.d.ts +0 -50
- package/phone-preview-rcs-channel.js +0 -319
- package/phone-preview-rcs-chat.js +0 -162
- package/phone-preview.js +0 -120
package/README.md
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# Nectary Labs
|
|
2
|
+
|
|
3
|
+
Welcome to Nectary Labs! This is the experimental playground for new components, patterns, and features that are being evaluated for inclusion in the main Nectary design system.
|
|
4
|
+
|
|
5
|
+
## 🎯 What is Nectary Labs?
|
|
6
|
+
|
|
7
|
+
Nectary Labs is a shared component library where **any team** can contribute their own experimental components. When a component is requested or used by multiple teams, it becomes a candidate for promotion to the main Nectary design system by the official Nectary team.
|
|
8
|
+
|
|
9
|
+
Think of it as an **incubator** where teams can share components with each other, and the most useful ones graduate to become officially supported in the main Nectary repository based on cross-team adoption.
|
|
10
|
+
|
|
11
|
+
## 🚀 Getting Started
|
|
12
|
+
|
|
13
|
+
### Development Setup
|
|
14
|
+
|
|
15
|
+
1. **Clone the repository**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git clone <repository-url>
|
|
19
|
+
cd nectary
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
2. **Install dependencies**
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
3. **Start the docs locally**
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm start
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
4. **Create documentation page for testing**
|
|
35
|
+
|
|
36
|
+
Create a documentation page in `docs/latest/src/pages/labComponents/YourComponent/` to display and manually test your component. This is essential for development and validation.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// docs/latest/src/pages/labComponents/YourComponent/examples/Basic.tsx
|
|
40
|
+
import '@nectary/labs/your-component'
|
|
41
|
+
|
|
42
|
+
export const BasicExample = () => (
|
|
43
|
+
<sinch-labs-your-component
|
|
44
|
+
text="Hello World"
|
|
45
|
+
disabled={false}
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 📝 Creating a New Component
|
|
51
|
+
|
|
52
|
+
### 1. Component Structure
|
|
53
|
+
|
|
54
|
+
Create a new directory following the naming convention:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
mkdir my-new-component
|
|
58
|
+
cd my-new-component
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 2. Create Documentation Page
|
|
62
|
+
|
|
63
|
+
**First**, create a documentation page in `docs/latest/src/pages/labComponents/MyComponent/` to display and manually test your component during development:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// docs/latest/src/pages/labComponents/MyComponent/examples/Basic.tsx
|
|
67
|
+
import '@nectary/labs/my-new-component'
|
|
68
|
+
|
|
69
|
+
export const BasicExample = () => (
|
|
70
|
+
<sinch-labs-my-new-component
|
|
71
|
+
text="Hello World"
|
|
72
|
+
disabled={false}
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Create the TypeScript File (index.ts)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { defineCustomElement, NectaryElement } from '../utils'
|
|
81
|
+
import templateHTML from './template.html'
|
|
82
|
+
import type React from 'react'
|
|
83
|
+
|
|
84
|
+
const template = document.createElement('template')
|
|
85
|
+
template.innerHTML = templateHTML
|
|
86
|
+
|
|
87
|
+
export class MyNewComponent extends NectaryElement {
|
|
88
|
+
// Private fields for DOM elements
|
|
89
|
+
#button: HTMLButtonElement
|
|
90
|
+
#controller: AbortController | null = null
|
|
91
|
+
|
|
92
|
+
constructor() {
|
|
93
|
+
super()
|
|
94
|
+
|
|
95
|
+
const shadowRoot = this.attachShadow()
|
|
96
|
+
shadowRoot.appendChild(template.content.cloneNode(true))
|
|
97
|
+
|
|
98
|
+
// Query DOM elements
|
|
99
|
+
this.#button = shadowRoot.querySelector('#button')!
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
connectedCallback() {
|
|
103
|
+
super.connectedCallback()
|
|
104
|
+
|
|
105
|
+
this.#controller = new AbortController()
|
|
106
|
+
const { signal } = this.#controller
|
|
107
|
+
|
|
108
|
+
// Add event listeners
|
|
109
|
+
this.#button.addEventListener('click', this.#onClick, { signal })
|
|
110
|
+
|
|
111
|
+
this.#updateUI()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
disconnectedCallback() {
|
|
115
|
+
super.disconnectedCallback()
|
|
116
|
+
this.#controller?.abort()
|
|
117
|
+
this.#controller = null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static get observedAttributes() {
|
|
121
|
+
return ['disabled', 'text']
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null) {
|
|
125
|
+
if (oldVal === newVal) return
|
|
126
|
+
|
|
127
|
+
switch (name) {
|
|
128
|
+
case 'disabled':
|
|
129
|
+
case 'text':
|
|
130
|
+
this.#updateUI()
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Properties with getters/setters
|
|
136
|
+
get disabled(): boolean {
|
|
137
|
+
return this.hasAttribute('disabled')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
set disabled(value: boolean) {
|
|
141
|
+
if (value) {
|
|
142
|
+
this.setAttribute('disabled', '')
|
|
143
|
+
} else {
|
|
144
|
+
this.removeAttribute('disabled')
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get text(): string {
|
|
149
|
+
return this.getAttribute('text') ?? ''
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
set text(value: string) {
|
|
153
|
+
this.setAttribute('text', value)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#updateUI() {
|
|
157
|
+
if (!this.isDomConnected) return
|
|
158
|
+
|
|
159
|
+
this.#button.disabled = this.disabled
|
|
160
|
+
this.#button.textContent = this.text
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#onClick = () => {
|
|
164
|
+
this.dispatchEvent(new CustomEvent('-click'))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
defineCustomElement('sinch-labs-my-new-component', MyNewComponent)
|
|
169
|
+
|
|
170
|
+
// TypeScript definitions
|
|
171
|
+
type Props = {
|
|
172
|
+
disabled?: boolean
|
|
173
|
+
text?: string
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
type ElementProps = Partial<{ [K in keyof Props]: Props[K] | string }>
|
|
177
|
+
|
|
178
|
+
declare global {
|
|
179
|
+
interface HTMLElementTagNameMap {
|
|
180
|
+
'sinch-labs-my-new-component': ElementProps & HTMLElement
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
declare module 'react' {
|
|
185
|
+
namespace JSX {
|
|
186
|
+
interface IntrinsicElements {
|
|
187
|
+
'sinch-labs-my-new-component': ElementProps &
|
|
188
|
+
React.ClassAttributes<HTMLElement> &
|
|
189
|
+
React.HTMLAttributes<HTMLElement>
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 4. Create the Template File (template.html)
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
<style>
|
|
199
|
+
:host {
|
|
200
|
+
display: inline-block;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#button {
|
|
204
|
+
padding: 8px 16px;
|
|
205
|
+
border: 1px solid var(--sinch-sys-color-border-default);
|
|
206
|
+
border-radius: 4px;
|
|
207
|
+
background: var(--sinch-sys-color-surface-default);
|
|
208
|
+
color: var(--sinch-sys-color-text-default);
|
|
209
|
+
font: var(--sinch-sys-font-body-m);
|
|
210
|
+
cursor: pointer;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#button:hover {
|
|
214
|
+
background: var(--sinch-sys-color-surface-hover);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#button:disabled {
|
|
218
|
+
opacity: 0.5;
|
|
219
|
+
cursor: not-allowed;
|
|
220
|
+
}
|
|
221
|
+
</style>
|
|
222
|
+
|
|
223
|
+
<button id="button" type="button">
|
|
224
|
+
Default Text
|
|
225
|
+
</button>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## 🎨 Design Patterns
|
|
229
|
+
|
|
230
|
+
### Use Primitive Props and Slots for Composition
|
|
231
|
+
|
|
232
|
+
Only use primitive types (string, number, boolean) for component properties to keep components as close to native HTML as possible. For complex data structures, prefer slots/children over array props for better composability:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// ❌ Avoid complex props
|
|
236
|
+
<my-component config={{theme: "dark", items: [1, 2, 3]}} />
|
|
237
|
+
<my-component items={[{title: "Item 1"}, {title: "Item 2"}]} />
|
|
238
|
+
|
|
239
|
+
// ✅ Use primitive props and slotted children
|
|
240
|
+
<my-component theme="dark" count="3">
|
|
241
|
+
<my-item title="Item 1" />
|
|
242
|
+
<my-item title="Item 2" />
|
|
243
|
+
</my-component>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Event Naming Convention
|
|
247
|
+
|
|
248
|
+
Use the `-` prefix for custom events:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
// Dispatch custom events
|
|
252
|
+
this.dispatchEvent(new CustomEvent('-click'))
|
|
253
|
+
this.dispatchEvent(new CustomEvent('-change', { detail: newValue }))
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Attribute Reflection
|
|
257
|
+
|
|
258
|
+
Always reflect important properties as attributes:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
get disabled(): boolean {
|
|
262
|
+
return this.hasAttribute('disabled')
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
set disabled(value: boolean) {
|
|
266
|
+
if (value) {
|
|
267
|
+
this.setAttribute('disabled', '')
|
|
268
|
+
} else {
|
|
269
|
+
this.removeAttribute('disabled')
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Use CSS Custom Properties
|
|
275
|
+
|
|
276
|
+
Leverage design tokens for consistent styling:
|
|
277
|
+
|
|
278
|
+
```css
|
|
279
|
+
:host {
|
|
280
|
+
color: var(--sinch-sys-color-text-default);
|
|
281
|
+
font: var(--sinch-sys-font-body-m);
|
|
282
|
+
background: var(--sinch-sys-color-surface-default);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## 🚢 Submission Guidelines
|
|
287
|
+
|
|
288
|
+
### Commit Messages
|
|
289
|
+
|
|
290
|
+
Follow conventional commit format:
|
|
291
|
+
|
|
292
|
+
```text
|
|
293
|
+
feat(labs): add new component for data visualization
|
|
294
|
+
fix(labs): resolve accessibility issue in phone preview
|
|
295
|
+
docs(labs): update contributing guide with new patterns
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
We use [semantic-release](https://semantic-release.gitbook.io/) for automated versioning and publishing. Your commit messages directly determine the version bump:
|
|
299
|
+
|
|
300
|
+
- **`feat:`** triggers a **minor** version bump (e.g., 1.2.0 → 1.3.0)
|
|
301
|
+
- **`fix:`** triggers a **patch** version bump (e.g., 1.2.0 → 1.2.1)
|
|
302
|
+
- **`docs:`**, **`style:`**, **`refactor:`** etc. don't trigger a release
|
|
303
|
+
- **Breaking changes** (with `BREAKING CHANGE:` in footer) trigger a **major** version bump (e.g., 1.2.0 → 2.0.0)
|
|
304
|
+
|
|
305
|
+
This means your commit message format is crucial for proper versioning and release notes generation.
|
|
306
|
+
|
|
307
|
+
### Pull Request Process
|
|
308
|
+
|
|
309
|
+
1. **Create Feature Branch**: `git checkout -b feat/my-new-component`
|
|
310
|
+
2. **Implement Component**: Follow the patterns above
|
|
311
|
+
3. **Test Thoroughly**: Run build, lint, and manual tests
|
|
312
|
+
4. **Update Documentation**: Add usage examples
|
|
313
|
+
5. **Submit PR**: Include clear description and testing notes
|
|
314
|
+
|
|
315
|
+
### Code Review Criteria
|
|
316
|
+
|
|
317
|
+
- **Architecture**: Follows Nectary Labs patterns
|
|
318
|
+
- **Performance**: Efficient event handling and DOM updates
|
|
319
|
+
- **Accessibility**: Proper ARIA attributes and keyboard support
|
|
320
|
+
- **Design**: Consistent with design system tokens
|
|
321
|
+
- **Documentation**: Clear examples and API documentation
|
|
322
|
+
|
|
323
|
+
## 🐛 Troubleshooting
|
|
324
|
+
|
|
325
|
+
### Common Issues
|
|
326
|
+
|
|
327
|
+
**TypeScript Conflicts**: If you see "Subsequent property declarations must have the same type":
|
|
328
|
+
|
|
329
|
+
- Remove any old compiled `.d.ts` files
|
|
330
|
+
- Run `npm run build` to regenerate types
|
|
331
|
+
- Restart TypeScript service in your editor
|
|
332
|
+
|
|
333
|
+
**Import Errors**: Ensure all child components are properly imported:
|
|
334
|
+
|
|
335
|
+
- Check import paths are correct
|
|
336
|
+
- Verify component registration
|
|
337
|
+
- Import child components before parent components
|
|
338
|
+
|
|
339
|
+
**Styling Issues**:
|
|
340
|
+
|
|
341
|
+
- Use `:host` for component root styles
|
|
342
|
+
- Ensure CSS custom properties are defined
|
|
343
|
+
- Check shadow DOM style encapsulation
|
|
344
|
+
|
|
345
|
+
## 💬 Getting Help
|
|
346
|
+
|
|
347
|
+
- Check existing components in `labs/` for patterns and examples
|
|
348
|
+
- Review the main Nectary design system documentation
|
|
349
|
+
- Ask questions in #nectary channels or discussions
|
|
350
|
+
- Browse the [Lab Components](/labComponents) section for live examples
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
**Ready to contribute?** Start by exploring existing components in the repository, then follow the patterns above to create your own experimental component that could benefit teams across the organization!
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import '@nectary/components/color-menu';
|
|
2
|
+
import '@nectary/components/color-menu-option';
|
|
3
|
+
import '@nectary/components/color-swatch';
|
|
4
|
+
import '@nectary/components/popover';
|
|
5
|
+
import '@nectary/components/select-button';
|
|
6
|
+
import { NectaryElement } from '../utils';
|
|
7
|
+
import type React from 'react';
|
|
8
|
+
export declare class ColorSelect extends NectaryElement {
|
|
9
|
+
#private;
|
|
10
|
+
constructor();
|
|
11
|
+
connectedCallback(): void;
|
|
12
|
+
disconnectedCallback(): void;
|
|
13
|
+
static get observedAttributes(): string[];
|
|
14
|
+
attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
|
|
15
|
+
get open(): boolean;
|
|
16
|
+
set open(value: boolean);
|
|
17
|
+
get value(): string;
|
|
18
|
+
set value(newValue: string);
|
|
19
|
+
}
|
|
20
|
+
type Props = {
|
|
21
|
+
open?: boolean;
|
|
22
|
+
value?: string;
|
|
23
|
+
};
|
|
24
|
+
type ElementProps = Partial<{
|
|
25
|
+
[K in keyof Props]: Props[K] | string;
|
|
26
|
+
}>;
|
|
27
|
+
declare global {
|
|
28
|
+
interface HTMLElementTagNameMap {
|
|
29
|
+
'sinch-labs-color-select': ElementProps & HTMLElement;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
declare module 'react' {
|
|
33
|
+
namespace JSX {
|
|
34
|
+
interface IntrinsicElements {
|
|
35
|
+
'sinch-labs-color-select': ElementProps & React.ClassAttributes<HTMLElement> & React.HTMLAttributes<HTMLElement>;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import '@nectary/components/color-menu';
|
|
2
|
+
import '@nectary/components/color-menu-option';
|
|
3
|
+
import '@nectary/components/color-swatch';
|
|
4
|
+
import '@nectary/components/popover';
|
|
5
|
+
import '@nectary/components/select-button';
|
|
6
|
+
import { defineCustomElement, NectaryElement } from '../utils';
|
|
7
|
+
import templateHTML from './template.html';
|
|
8
|
+
const template = document.createElement('template');
|
|
9
|
+
template.innerHTML = templateHTML;
|
|
10
|
+
export class ColorSelect extends NectaryElement {
|
|
11
|
+
#popover;
|
|
12
|
+
#selectButton;
|
|
13
|
+
#colorSwatch;
|
|
14
|
+
#colorMenu;
|
|
15
|
+
#controller = null;
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
const shadowRoot = this.attachShadow();
|
|
19
|
+
shadowRoot.appendChild(template.content.cloneNode(true));
|
|
20
|
+
this.#popover = shadowRoot.querySelector('sinch-popover');
|
|
21
|
+
this.#selectButton = shadowRoot.querySelector('sinch-select-button');
|
|
22
|
+
this.#colorSwatch = shadowRoot.querySelector('sinch-color-swatch');
|
|
23
|
+
this.#colorMenu = shadowRoot.querySelector('sinch-color-menu');
|
|
24
|
+
}
|
|
25
|
+
connectedCallback() {
|
|
26
|
+
super.connectedCallback();
|
|
27
|
+
this.#controller = new AbortController();
|
|
28
|
+
const { signal } = this.#controller;
|
|
29
|
+
this.#selectButton.addEventListener('-click', this.#onOpen, { signal });
|
|
30
|
+
this.#popover.addEventListener('-close', this.#onClose, { signal });
|
|
31
|
+
this.#colorMenu.addEventListener('-change', this.#onChange, { signal });
|
|
32
|
+
this.#updateUI();
|
|
33
|
+
}
|
|
34
|
+
disconnectedCallback() {
|
|
35
|
+
super.disconnectedCallback();
|
|
36
|
+
this.#controller?.abort();
|
|
37
|
+
this.#controller = null;
|
|
38
|
+
}
|
|
39
|
+
static get observedAttributes() {
|
|
40
|
+
return ['open', 'value'];
|
|
41
|
+
}
|
|
42
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
43
|
+
if (oldVal === newVal) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
switch (name) {
|
|
47
|
+
case 'open':
|
|
48
|
+
case 'value': {
|
|
49
|
+
this.#updateUI();
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
get open() {
|
|
55
|
+
return this.hasAttribute('open');
|
|
56
|
+
}
|
|
57
|
+
set open(value) {
|
|
58
|
+
if (value) {
|
|
59
|
+
this.setAttribute('open', '');
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
this.removeAttribute('open');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
get value() {
|
|
66
|
+
return this.getAttribute('value') ?? '';
|
|
67
|
+
}
|
|
68
|
+
set value(newValue) {
|
|
69
|
+
if (newValue !== '') {
|
|
70
|
+
this.setAttribute('value', newValue);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.removeAttribute('value');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
#updateUI() {
|
|
77
|
+
if (!this.isDomConnected) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.#popover.toggleAttribute('open', this.open);
|
|
81
|
+
this.#selectButton.setAttribute('text', this.value);
|
|
82
|
+
this.#colorSwatch.setAttribute('name', this.value);
|
|
83
|
+
this.#colorMenu.setAttribute('value', this.value);
|
|
84
|
+
}
|
|
85
|
+
#onClose = () => {
|
|
86
|
+
this.open = false;
|
|
87
|
+
};
|
|
88
|
+
#onOpen = () => {
|
|
89
|
+
this.open = true;
|
|
90
|
+
};
|
|
91
|
+
#onChange = (e) => {
|
|
92
|
+
this.value = e.detail;
|
|
93
|
+
this.dispatchEvent(new CustomEvent('-change', { detail: e.detail }));
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
defineCustomElement('sinch-labs-color-select', ColorSelect);
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nectary/labs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"**/*/*.css",
|
|
6
6
|
"**/*/*.json",
|
|
@@ -16,15 +16,11 @@
|
|
|
16
16
|
"build": "NODE_ENV=production && rimraf lib/ && tsc"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@
|
|
20
|
-
"@nectary/components": "4.10.3",
|
|
21
|
-
"solid-element": "1.8.1",
|
|
22
|
-
"solid-js": "1.8.19"
|
|
19
|
+
"@nectary/components": "4.10.3"
|
|
23
20
|
},
|
|
24
21
|
"devDependencies": {
|
|
25
22
|
"@types/node": "^18.17.17",
|
|
26
23
|
"@types/react": "^18.2.21",
|
|
27
|
-
"lit": "^3.1.2",
|
|
28
24
|
"rimraf": "^3.0.2",
|
|
29
25
|
"ts-node": "^10.9.1",
|
|
30
26
|
"typescript": "^5.2.2"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { NectaryElement } from '../utils';
|
|
2
|
+
import type React from 'react';
|
|
1
3
|
/**
|
|
2
4
|
* Container for channel previews in a styled phone container.
|
|
3
5
|
* This container uses a custom scaling where the internal elements are scaled to fit the container from a fixed size.
|
|
@@ -7,13 +9,22 @@
|
|
|
7
9
|
* @param props.clock Clock `Intl.DateTimeFormat` options.
|
|
8
10
|
* @param props.children Content to display in the phone container.
|
|
9
11
|
*/
|
|
10
|
-
declare
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
export declare class PhonePreview extends NectaryElement {
|
|
13
|
+
#private;
|
|
14
|
+
constructor();
|
|
15
|
+
connectedCallback(): void;
|
|
16
|
+
disconnectedCallback(): void;
|
|
17
|
+
static get observedAttributes(): string[];
|
|
18
|
+
attributeChangedCallback(): void;
|
|
19
|
+
get locale(): string;
|
|
20
|
+
set locale(value: string);
|
|
21
|
+
get clockOptions(): Intl.DateTimeFormatOptions;
|
|
22
|
+
set clockOptions(value: Intl.DateTimeFormatOptions);
|
|
23
|
+
}
|
|
24
|
+
type Props = {
|
|
25
|
+
locale?: string;
|
|
26
|
+
clock?: Intl.DateTimeFormatOptions | string;
|
|
27
|
+
};
|
|
17
28
|
type ElementProps = Partial<{
|
|
18
29
|
[K in keyof Props]: Props[K] | string;
|
|
19
30
|
}>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { defineCustomElement, NectaryElement } from '../utils';
|
|
2
|
+
import templateHTML from './template.html';
|
|
3
|
+
const template = document.createElement('template');
|
|
4
|
+
template.innerHTML = templateHTML;
|
|
5
|
+
/**
|
|
6
|
+
* Container for channel previews in a styled phone container.
|
|
7
|
+
* This container uses a custom scaling where the internal elements are scaled to fit the container from a fixed size.
|
|
8
|
+
* Because of the fixed size, absolute units (px) are preferred over relative units (rem, em) for the internal elements.
|
|
9
|
+
*
|
|
10
|
+
* @param props.locale Clock locale.
|
|
11
|
+
* @param props.clock Clock `Intl.DateTimeFormat` options.
|
|
12
|
+
* @param props.children Content to display in the phone container.
|
|
13
|
+
*/
|
|
14
|
+
export class PhonePreview extends NectaryElement {
|
|
15
|
+
#clockElement;
|
|
16
|
+
#resizeObserver;
|
|
17
|
+
#clockInterval = null;
|
|
18
|
+
#formatter;
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
const shadowRoot = this.attachShadow();
|
|
22
|
+
shadowRoot.appendChild(template.content.cloneNode(true));
|
|
23
|
+
this.#clockElement = shadowRoot.querySelector('#clock');
|
|
24
|
+
this.#resizeObserver = new ResizeObserver(() => {
|
|
25
|
+
this.#updateScale();
|
|
26
|
+
});
|
|
27
|
+
this.#formatter = new Intl.DateTimeFormat(this.locale, this.clockOptions);
|
|
28
|
+
}
|
|
29
|
+
connectedCallback() {
|
|
30
|
+
super.connectedCallback();
|
|
31
|
+
const section = this.shadowRoot.querySelector('section');
|
|
32
|
+
this.#resizeObserver.observe(this);
|
|
33
|
+
this.#resizeObserver.observe(section);
|
|
34
|
+
this.#startClock();
|
|
35
|
+
this.#updateClock();
|
|
36
|
+
}
|
|
37
|
+
disconnectedCallback() {
|
|
38
|
+
super.disconnectedCallback();
|
|
39
|
+
this.#resizeObserver.disconnect();
|
|
40
|
+
this.#stopClock();
|
|
41
|
+
}
|
|
42
|
+
static get observedAttributes() {
|
|
43
|
+
return ['locale', 'clock'];
|
|
44
|
+
}
|
|
45
|
+
attributeChangedCallback() {
|
|
46
|
+
this.#formatter = new Intl.DateTimeFormat(this.locale, this.clockOptions);
|
|
47
|
+
this.#updateClock();
|
|
48
|
+
}
|
|
49
|
+
get locale() {
|
|
50
|
+
return this.getAttribute('locale') ?? 'en-US';
|
|
51
|
+
}
|
|
52
|
+
set locale(value) {
|
|
53
|
+
this.setAttribute('locale', value);
|
|
54
|
+
}
|
|
55
|
+
get clockOptions() {
|
|
56
|
+
const clockAttr = this.getAttribute('clock');
|
|
57
|
+
if (clockAttr !== null && clockAttr !== '') {
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(clockAttr);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Fallback to default if JSON parsing fails
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { hour: '2-digit', minute: '2-digit' };
|
|
66
|
+
}
|
|
67
|
+
set clockOptions(value) {
|
|
68
|
+
this.setAttribute('clock', JSON.stringify(value));
|
|
69
|
+
}
|
|
70
|
+
#updateScale() {
|
|
71
|
+
const style = getComputedStyle(this);
|
|
72
|
+
const baseSize = parseFloat(style.getPropertyValue('--base-size'));
|
|
73
|
+
const currentSize = this.getBoundingClientRect().width;
|
|
74
|
+
this.style.setProperty('--scale', `${currentSize / baseSize}`);
|
|
75
|
+
}
|
|
76
|
+
#startClock() {
|
|
77
|
+
this.#stopClock();
|
|
78
|
+
this.#clockInterval = window.setInterval(() => {
|
|
79
|
+
this.#updateClock();
|
|
80
|
+
}, 60000);
|
|
81
|
+
}
|
|
82
|
+
#stopClock() {
|
|
83
|
+
if (this.#clockInterval !== null) {
|
|
84
|
+
clearInterval(this.#clockInterval);
|
|
85
|
+
this.#clockInterval = null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
#updateClock() {
|
|
89
|
+
if (this.isDomConnected) {
|
|
90
|
+
this.#clockElement.textContent = this.#formatter.format(new Date());
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
defineCustomElement('sinch-labs-phone-preview', PhonePreview);
|