@nectary/labs
v2.5.41
Published
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.
Readme
Nectary Labs
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.
🎯 What is Nectary Labs?
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.
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.
🚀 Getting Started
Development Setup
Clone the repository
git clone <repository-url> cd nectaryInstall dependencies
pnpm installStart the docs locally
pnpm startCreate documentation page for testing
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.// docs/latest/src/pages/labComponents/YourComponent/examples/Basic.tsx import '@nectary/labs/your-component' export const BasicExample = () => ( <sinch-labs-your-component text="Hello World" disabled={false} /> )
📝 Creating a New Component
1. Component Structure
Create a new directory following the naming convention:
mkdir my-new-component
cd my-new-component2. Create Documentation Page
First, create a documentation page in docs/latest/src/pages/labComponents/MyComponent/ to display and manually test your component during development:
// docs/latest/src/pages/labComponents/MyComponent/examples/Basic.tsx
import '@nectary/labs/my-new-component'
export const BasicExample = () => (
<sinch-labs-my-new-component
text="Hello World"
disabled={false}
/>
)3. Create the TypeScript File (index.ts)
import { defineCustomElement, NectaryElement } from '../utils'
import templateHTML from './template.html'
import type React from 'react'
const template = document.createElement('template')
template.innerHTML = templateHTML
export class MyNewComponent extends NectaryElement {
// Private fields for DOM elements
#button: HTMLButtonElement
#controller: AbortController | null = null
constructor() {
super()
const shadowRoot = this.attachShadow()
shadowRoot.appendChild(template.content.cloneNode(true))
// Query DOM elements
this.#button = shadowRoot.querySelector('#button')!
}
connectedCallback() {
super.connectedCallback()
this.#controller = new AbortController()
const { signal } = this.#controller
// Add event listeners
this.#button.addEventListener('click', this.#onClick, { signal })
this.#updateUI()
}
disconnectedCallback() {
super.disconnectedCallback()
this.#controller?.abort()
this.#controller = null
}
static get observedAttributes() {
return ['disabled', 'text']
}
attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null) {
if (oldVal === newVal) return
switch (name) {
case 'disabled':
case 'text':
this.#updateUI()
break
}
}
// Properties with getters/setters
get disabled(): boolean {
return this.hasAttribute('disabled')
}
set disabled(value: boolean) {
if (value) {
this.setAttribute('disabled', '')
} else {
this.removeAttribute('disabled')
}
}
get text(): string {
return this.getAttribute('text') ?? ''
}
set text(value: string) {
this.setAttribute('text', value)
}
#updateUI() {
if (!this.isDomConnected) return
this.#button.disabled = this.disabled
this.#button.textContent = this.text
}
#onClick = () => {
this.dispatchEvent(new CustomEvent('-click'))
}
}
defineCustomElement('sinch-labs-my-new-component', MyNewComponent)
// TypeScript definitions
type Props = {
disabled?: boolean
text?: string
}
type ElementProps = Partial<{ [K in keyof Props]: Props[K] | string }>
declare global {
interface HTMLElementTagNameMap {
'sinch-labs-my-new-component': ElementProps & HTMLElement
}
}
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'sinch-labs-my-new-component': ElementProps &
React.ClassAttributes<HTMLElement> &
React.HTMLAttributes<HTMLElement>
}
}
}4. Create the Template File (template.html)
<style>
:host {
display: inline-block;
}
#button {
padding: 8px 16px;
border: 1px solid var(--sinch-sys-color-border-default);
border-radius: 4px;
background: var(--sinch-sys-color-surface-default);
color: var(--sinch-sys-color-text-default);
font: var(--sinch-sys-font-body-m);
cursor: pointer;
}
#button:hover {
background: var(--sinch-sys-color-surface-hover);
}
#button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
<button id="button" type="button">
Default Text
</button>🎨 Design Patterns
Use Primitive Props and Slots for Composition
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:
// ❌ Avoid complex props
<my-component config={{theme: "dark", items: [1, 2, 3]}} />
<my-component items={[{title: "Item 1"}, {title: "Item 2"}]} />
// ✅ Use primitive props and slotted children
<my-component theme="dark" count="3">
<my-item title="Item 1" />
<my-item title="Item 2" />
</my-component>Event Naming Convention
Use the - prefix for custom events:
// Dispatch custom events
this.dispatchEvent(new CustomEvent('-click'))
this.dispatchEvent(new CustomEvent('-change', { detail: newValue }))Attribute Reflection
Always reflect important properties as attributes:
get disabled(): boolean {
return this.hasAttribute('disabled')
}
set disabled(value: boolean) {
if (value) {
this.setAttribute('disabled', '')
} else {
this.removeAttribute('disabled')
}
}Use CSS Custom Properties
Leverage design tokens for consistent styling:
:host {
color: var(--sinch-sys-color-text-default);
font: var(--sinch-sys-font-body-m);
background: var(--sinch-sys-color-surface-default);
}🚢 Submission Guidelines
Commit Messages
Follow conventional commit format:
feat(labs): add new component for data visualization
fix(labs): resolve accessibility issue in phone preview
docs(labs): update contributing guide with new patternsWe use semantic-release for automated versioning and publishing. Your commit messages directly determine the version bump:
feat:triggers a minor version bump (e.g., 1.2.0 → 1.3.0)fix:triggers a patch version bump (e.g., 1.2.0 → 1.2.1)docs:,style:,refactor:etc. don't trigger a release- Breaking changes (with
BREAKING CHANGE:in footer) trigger a major version bump (e.g., 1.2.0 → 2.0.0)
This means your commit message format is crucial for proper versioning and release notes generation.
Pull Request Process
- Create Feature Branch:
git checkout -b feat/my-new-component - Implement Component: Follow the patterns above
- Test Thoroughly: Run build, lint, and manual tests
- Update Documentation: Add usage examples
- Submit PR: Include clear description and testing notes
Code Review Criteria
- Architecture: Follows Nectary Labs patterns
- Performance: Efficient event handling and DOM updates
- Accessibility: Proper ARIA attributes and keyboard support
- Design: Consistent with design system tokens
- Documentation: Clear examples and API documentation
🐛 Troubleshooting
Common Issues
TypeScript Conflicts: If you see "Subsequent property declarations must have the same type":
- Remove any old compiled
.d.tsfiles - Run
npm run buildto regenerate types - Restart TypeScript service in your editor
Import Errors: Ensure all child components are properly imported:
- Check import paths are correct
- Verify component registration
- Import child components before parent components
Styling Issues:
- Use
:hostfor component root styles - Ensure CSS custom properties are defined
- Check shadow DOM style encapsulation
💬 Getting Help
- Check existing components in
labs/for patterns and examples - Review the main Nectary design system documentation
- Ask questions in #nectary channels or discussions
- Browse the Lab Components section for live examples
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!
