Examples & Tutorials
This section provides practical examples and step-by-step tutorials to help you master STX.
Basic Examples
Counter Component
A simple counter component demonstrating state management and events:
html
@ts
interface Props {
initialCount?: number
}
@endts
@component('Counter', {
props: {
initialCount: {
type: Number,
default: 0
}
},
setup(props) {
const count = ref(props.initialCount)
const increment = () => count.value++
const decrement = () => count.value--
return { count, increment, decrement }
}
})
<div class="counter">
<button @click="decrement">-</button>
<span>{{ count }}</span>
<button @click="increment">+</button>
</div>
<style scoped>
.counter {
@apply flex items-center gap-4;
}
button {
@apply px-4 py-2 bg-blue-500 text-white rounded;
}
span {
@apply text-xl font-bold;
}
</style>
@endcomponent
Todo List
A complete todo list application showing component composition and state management:
html
@ts
interface Todo {
id: number
text: string
completed: boolean
}
interface TodoListProps {
title?: string
}
@endts
@component('TodoList', {
props: {
title: {
type: String,
default: 'Todo List'
}
},
setup() {
const todos = ref<Todo[]>([])
const newTodo = ref('')
const addTodo = () => {
if (!newTodo.value.trim()) return
todos.value.push({
id: Date.now(),
text: newTodo.value,
completed: false
})
newTodo.value = ''
}
const toggleTodo = (id: number) => {
const todo = todos.value.find(t => t.id === id)
if (todo) todo.completed = !todo.completed
}
const removeTodo = (id: number) => {
todos.value = todos.value.filter(t => t.id !== id)
}
return {
todos,
newTodo,
addTodo,
toggleTodo,
removeTodo
}
}
})
<div class="todo-list">
<h2>{{ title }}</h2>
<div class="add-todo">
<input
type="text"
v-model="newTodo"
@keyup.enter="addTodo"
placeholder="Add new todo"
>
<button @click="addTodo">Add</button>
</div>
<ul>
@foreach(todos as todo)
<li :class="{ completed: todo.completed }">
<input
type="checkbox"
:checked="todo.completed"
@change="toggleTodo(todo.id)"
>
<span>{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">Delete</button>
</li>
@endforeach
</ul>
</div>
<style scoped>
.todo-list {
@apply max-w-md mx-auto p-4;
}
.add-todo {
@apply flex gap-2 mb-4;
}
input[type="text"] {
@apply flex-1 px-4 py-2 border rounded;
}
button {
@apply px-4 py-2 bg-blue-500 text-white rounded;
}
ul {
@apply space-y-2;
}
li {
@apply flex items-center gap-2 p-2 border rounded;
}
.completed span {
@apply line-through text-gray-500;
}
</style>
@endcomponent
Tutorials
Building a Blog
1. Project Setup
First, create a new STX project:
bash
# Create new project
bunx create-stx-app my-blog
cd my-blog
# Install dependencies
bun install
2. Create Post Component
Create a reusable post component (components/Post.stx
):
html
@ts
interface Post {
id: number
title: string
content: string
author: string
date: string
}
interface Props {
post: Post
}
@endts
@component('Post', {
props: {
post: {
type: Object as PropType<Post>,
required: true
}
}
})
<article class="post">
<h2>{{ post.title }}</h2>
<div class="meta">
<span>By {{ post.author }}</span>
<span>{{ post.date }}</span>
</div>
<div class="content">
{{ post.content }}
</div>
</article>
<style scoped>
.post {
@apply max-w-2xl mx-auto p-6 bg-white rounded shadow;
}
h2 {
@apply text-2xl font-bold mb-4;
}
.meta {
@apply text-sm text-gray-500 mb-4;
}
.content {
@apply prose;
}
</style>
@endcomponent
3. Create Blog Layout
Create a layout component (layouts/Blog.stx
):
html
@component('BlogLayout')
<div class="blog-layout">
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main>
<slot></slot>
</main>
<footer>
<p>© 2024 My Blog</p>
</footer>
</div>
<style>
.blog-layout {
@apply min-h-screen flex flex-col;
}
header {
@apply bg-white shadow;
}
nav {
@apply max-w-7xl mx-auto px-4 py-6 flex gap-4;
}
main {
@apply flex-1 max-w-7xl mx-auto px-4 py-8;
}
footer {
@apply bg-gray-100 py-6 text-center;
}
</style>
@endcomponent
4. Create Blog Home Page
Create the home page (pages/index.stx
):
html
@ts
import type { Post } from '../types'
interface Props {
posts: Post[]
}
@endts
@component('HomePage', {
props: {
posts: {
type: Array as PropType<Post[]>,
required: true
}
}
})
<BlogLayout>
<h1>Welcome to My Blog</h1>
<div class="posts">
@foreach(posts as post)
<Post :post="post" />
@endforeach
</div>
</BlogLayout>
<style scoped>
h1 {
@apply text-4xl font-bold mb-8 text-center;
}
.posts {
@apply space-y-8;
}
</style>
@endcomponent
5. Add State Management
Create a store for blog posts (stores/posts.ts
):
typescript
import { createStore } from '@stx/store'
import type { Post } from '../types'
export const usePostStore = createStore({
state: {
posts: [] as Post[]
},
actions: {
async fetchPosts() {
// Simulate API call
const response = await fetch('/api/posts')
this.posts = await response.json()
},
async addPost(post: Omit<Post, 'id'>) {
// Simulate API call
const response = await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(post)
})
const newPost = await response.json()
this.posts.push(newPost)
}
}
})
Advanced Patterns
Form Handling
Create a reusable form component with validation:
html
@ts
interface FormField {
name: string
type: string
label: string
required?: boolean
pattern?: string
minLength?: number
maxLength?: number
}
interface Props {
fields: FormField[]
onSubmit: (data: Record<string, any>) => void
}
@endts
@component('Form', {
props: {
fields: {
type: Array as PropType<FormField[]>,
required: true
},
onSubmit: {
type: Function as PropType<Props['onSubmit']>,
required: true
}
},
setup(props) {
const formData = ref<Record<string, any>>({})
const errors = ref<Record<string, string>>({})
const validate = () => {
errors.value = {}
props.fields.forEach(field => {
const value = formData.value[field.name]
if (field.required && !value) {
errors.value[field.name] = `${field.label} is required`
}
if (field.pattern && value && !new RegExp(field.pattern).test(value)) {
errors.value[field.name] = `${field.label} is invalid`
}
if (field.minLength && value && value.length < field.minLength) {
errors.value[field.name] = `${field.label} must be at least ${field.minLength} characters`
}
if (field.maxLength && value && value.length > field.maxLength) {
errors.value[field.name] = `${field.label} must be at most ${field.maxLength} characters`
}
})
return Object.keys(errors.value).length === 0
}
const handleSubmit = (e: Event) => {
e.preventDefault()
if (validate()) {
props.onSubmit(formData.value)
}
}
return {
formData,
errors,
handleSubmit
}
}
})
<form @submit="handleSubmit" class="form">
@foreach(fields as field)
<div class="form-field">
<label :for="field.name">{{ field.label }}</label>
<input
:id="field.name"
:type="field.type"
:name="field.name"
v-model="formData[field.name]"
:required="field.required"
:pattern="field.pattern"
:minlength="field.minLength"
:maxlength="field.maxLength"
>
@if(errors[field.name])
<span class="error">{{ errors[field.name] }}</span>
@endif
</div>
@endforeach
<button type="submit">Submit</button>
</form>
<style scoped>
.form {
@apply max-w-md mx-auto;
}
.form-field {
@apply mb-4;
}
label {
@apply block mb-2 font-medium;
}
input {
@apply w-full px-4 py-2 border rounded;
}
.error {
@apply text-sm text-red-500 mt-1;
}
button {
@apply w-full px-4 py-2 bg-blue-500 text-white rounded;
}
</style>
@endcomponent
Dynamic Components
Create a component that loads other components dynamically:
html
@ts
interface Props {
componentName: string
props?: Record<string, any>
}
@endts
@component('DynamicComponent', {
props: {
componentName: {
type: String,
required: true
},
props: {
type: Object,
default: () => ({})
}
},
async setup(props) {
const component = ref(null)
onMounted(async () => {
try {
component.value = await import(`../components/${props.componentName}.stx`)
} catch (error) {
console.error(`Failed to load component: ${props.componentName}`, error)
}
})
return { component }
}
})
@if(component)
<component :is="component" v-bind="props" />
@else
<div class="loading">Loading...</div>
@endif
<style scoped>
.loading {
@apply p-4 text-center text-gray-500;
}
</style>
@endcomponent
Best Practices
Component Organization
Follow these best practices for organizing components:
- Use a clear directory structure:
components/
├── common/ # Shared components
├── layout/ # Layout components
├── features/ # Feature-specific components
└── pages/ # Page components
- Keep components focused and single-responsibility
- Use TypeScript interfaces for props and events
- Document component usage with comments
Performance Optimization
Tips for optimizing STX applications:
- Use lazy loading for large components:
typescript
const MyLargeComponent = () => import('./MyLargeComponent.stx')
- Implement proper caching strategies:
typescript
const cachedData = useMemo(() => expensiveComputation(), [deps])
- Optimize renders with proper dependency tracking:
typescript
const derivedValue = computed(() => source.value * 2)
- Use efficient list rendering:
html
@foreach(items as item)
<component :key="item.id" :item="item" />
@endforeach