An open API service indexing awesome lists of open source software.

https://github.com/eldomagan/alpine-define-component


https://github.com/eldomagan/alpine-define-component

Last synced: 6 months ago
JSON representation

Awesome Lists containing this project

README

          

# alpine-define-component

Structured component system for Alpine.js with parts/slots support and full TypeScript support.

## Installation

```bash
npm install alpine-define-component
```

## Quick Start

```typescript
import Alpine from 'alpinejs';
import { defineComponent } from 'alpine-define-component';

const accordion = defineComponent({
name: 'accordion',
setup: (props) => ({
value: props.value || [],

toggle(id) {
const isOpen = this.value.includes(id);
this.value = isOpen ? this.value.filter((i) => i !== id) : [id];
},

isOpen(id) {
return this.value.includes(id);
},
}),

parts: {
item(api, el, { value }) {
return {
'x-on:click': () => api.toggle(value),
'x-bind:data-open': () => api.isOpen(value),
};
},
},
});

Alpine.plugin(accordion);
Alpine.start();
```

```html


Item 1

Item 2


```

## TypeScript

### `setup` Helper (Optional)

For TypeScript users who want Alpine magics (`this.$dispatch`, `this.$watch`, etc.) typed in methods:

```typescript
import { defineComponent, setup } from 'alpine-define-component';

const counter = defineComponent({
name: 'counter',
setup: setup((props: { count?: number }) => ({
count: props.count ?? 0,

increment() {
this.count++;
this.$dispatch('incremented');
},
})),
});
```

## Scoped Parts

Use `defineScope` to create isolated reactive contexts for repeating parts (like tabs, accordion items, list items):

```typescript
import { defineComponent, defineScope } from 'alpine-define-component';

const tabs = defineComponent({
name: 'tabs',
setup: (props) => ({
activeTab: props.defaultTab || 'tab1',
setTab(tab: string) {
this.activeTab = tab;
}
}),

parts: {
item: defineScope({
name: 'tabItem',
setup: (api, el, { value }) => ({
id: value,
isActive: () => api.activeTab === value
}),
bindings: (api, scope) => ({
'x-on:click': () => api.setTab(scope.id),
'x-bind:class': () => ({ active: scope.isActive() })
})
})
}
});
```

```html




Tab 1


Tab 2


```

**Benefits:**
- Isolated reactive state per part instance
- Access parent API and scope data together
- Scope available as `$scopeName` magic in HTML
- Perfect for lists, tabs, accordions, menu items, etc.

### Scope Typing (TypeScript)

For full type safety with scopes, use the parts-as-function pattern with `withScopes`:

```typescript
import { defineComponent, defineScope, setup } from 'alpine-define-component';

const accordion = defineComponent({
name: 'accordion',
setup: setup(() => ({
openItems: [] as string[],
toggle(itemId: string) { /* ... */ },
isOpen(itemId: string) { /* ... */ },
})),

parts: ({ withScopes }) => withScopes<{
$item: { id: string; isOpen: boolean; toggle: () => void };
}>({
item: defineScope({
name: 'item',
setup: (api, _, { value: itemId }) => ({
id: itemId,
isOpen: api.isOpen(itemId),
toggle() { api.toggle(itemId); },
}),
}),

header(api) {
// api.$item is correctly typed (from withScopes)
return {
'x-on:click': () => api.$item.toggle(),
'x-bind:class': () => ({ open: api.$item.isOpen }),
};
},
}),
});
```

## API

### `defineComponent(config)`

```typescript
defineComponent({
name: string, // Component/directive name
setup: (props, ctx) => {...}, // Returns component API
parts?: {...} // Optional part handlers
})
```

### `defineScope(options)`

Creates an isolated reactive scope for component parts.

```typescript
defineScope({
name: string, // Scope name (accessible as $name in template)
setup: (api, el, ctx) => {...}, // Returns scope data
bindings?: (api, scope) => {...} // Optional Alpine bindings
})
```

**Returns:** Part handler function

### `setup(fn)` (TypeScript)

Type helper for Alpine magics in methods.

## Inspiration

- [@alpine/ui](https://alpinejs.dev/components#headless)
- [alpine-zag](https://github.com/TunkShif/alpine-zag)

## License

MIT