@diniz/webcomponents 1.1.5 → 1.1.7

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/dist/README.md CHANGED
@@ -12,948 +12,761 @@ A lightweight, framework-agnostic web components library built with vanilla Type
12
12
  🎯 **Tree-shakeable** - Import only what you need
13
13
  ♿ **Accessible** - ARIA attributes and keyboard navigation
14
14
 
15
- ## 🚀 Live Demo
15
+ ## 🚀 Live Demo & Component Documentation
16
16
 
17
- Check out the interactive demo and component examples:
17
+ Check out the interactive demo and explore component implementations:
18
18
 
19
19
  **[View Live Demo →](https://rodiniz.github.io/webcomponents/)**
20
20
 
21
+ **Component source code and demos are located in the `src/features/` directory:**
22
+ - Button Demo: `src/features/button-demo/`
23
+ - Input Demo: `src/features/input-demo/`
24
+ - Table Demo: `src/features/table-demo/`
25
+ - Form Demo: `src/features/form-demo/`
26
+ - Date Picker Demo: `src/features/date-picker-demo/`
27
+ - And more...
28
+
29
+ Each demo includes the component implementation and usage examples. Visit the live demo site to see all components in action.
30
+
21
31
  ## Installation
22
32
 
23
33
  ```bash
24
34
  npm install @diniz/webcomponents
25
35
  ```
26
36
 
27
- ## Using with Vite (No Framework)
37
+ ## Quick Start
38
+
39
+ ```typescript
40
+ import '@diniz/webcomponents';
41
+ import '@diniz/webcomponents/dist/style.css';
42
+
43
+ // Components are now available
44
+ document.body.innerHTML = `
45
+ <ui-button variant="primary">Click Me</ui-button>
46
+ <ui-date-picker format="DD/MM/YYYY"></ui-date-picker>
47
+ `;
48
+ ```
28
49
 
29
- This library works seamlessly with Vite without requiring any framework. Here's how to set up a vanilla JavaScript/TypeScript project:
50
+ ## Quick Start with Vite (No Framework)
30
51
 
31
- ### 1. Create a New Vite Project
52
+ Create a new Vite project without any framework to use these web components:
53
+
54
+ ### 1. Create a new Vite project
32
55
 
33
56
  ```bash
34
- # Create a new Vite project with vanilla TypeScript template
35
57
  npm create vite@latest my-app -- --template vanilla-ts
36
58
  cd my-app
37
- npm install
38
59
  ```
39
60
 
40
- ### 2. Install the Library
61
+ ### 2. Install the web components library
41
62
 
42
63
  ```bash
43
64
  npm install @diniz/webcomponents
44
65
  ```
45
66
 
46
- ### 3. Import Components in Your Main File
47
-
48
- In your `src/main.ts` file:
67
+ ### 3. Import components in your `src/main.ts`
49
68
 
50
69
  ```typescript
51
70
  import '@diniz/webcomponents';
52
- import '@diniz/webcomponents/dist/style.css'; // Import styles
71
+ import '@diniz/webcomponents/dist/style.css';
72
+ import './style.css';
53
73
 
54
- // Now you can use the components in your HTML
55
74
  document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
56
75
  <div>
57
76
  <h1>My Web Components App</h1>
58
- <ui-button variant="primary" size="md">Click Me</ui-button>
59
- <ui-date-picker format="DD/MM/YYYY"></ui-date-picker>
77
+
78
+ <ui-button variant="primary">Primary Button</ui-button>
79
+ <ui-button variant="secondary">Secondary Button</ui-button>
80
+
81
+ <ui-input
82
+ label="Email"
83
+ type="email"
84
+ placeholder="Enter your email"
85
+ required
86
+ ></ui-input>
87
+
88
+ <ui-date-picker
89
+ label="Select Date"
90
+ format="DD/MM/YYYY"
91
+ ></ui-date-picker>
60
92
  </div>
61
93
  `;
62
- ```
63
-
64
- ### 4. Use Components in HTML
65
94
 
66
- In your `index.html`:
67
-
68
- ```html
69
- <!DOCTYPE html>
70
- <html lang="en">
71
- <head>
72
- <meta charset="UTF-8" />
73
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
74
- <title>My App</title>
75
- </head>
76
- <body>
77
- <div id="app">
78
- <ui-button variant="primary">Click Me</ui-button>
79
- <ui-date-picker format="DD/MM/YYYY"></ui-date-picker>
80
- <ui-table></ui-table>
81
- </div>
82
- <script type="module" src="/src/main.ts"></script>
83
- </body>
84
- </html>
85
- ```
86
-
87
- ### 5. Add Event Listeners (Optional)
88
-
89
- ```typescript
90
- // Wait for components to be defined
91
- customElements.whenDefined('ui-button').then(() => {
92
- const button = document.querySelector('ui-button');
93
- button?.addEventListener('click', () => {
94
- console.log('Button clicked!');
95
- });
95
+ // Listen to component events
96
+ document.querySelector('ui-button')?.addEventListener('click', () => {
97
+ console.log('Button clicked!');
96
98
  });
97
99
 
98
- // Listen to custom events
99
- const picker = document.querySelector('ui-date-picker');
100
- picker?.addEventListener('date-change', ((e: CustomEvent) => {
101
- console.log('Date selected:', e.detail.value);
102
- }) as EventListener);
103
- ```
104
-
105
- ### 6. TypeScript Support
106
-
107
- For full TypeScript support, create a `src/types.d.ts` file:
108
-
109
- ```typescript
110
- declare module '@diniz/webcomponents' {
111
- export interface UIButton extends HTMLElement {
112
- variant: 'primary' | 'secondary' | 'ghost';
113
- size: 'sm' | 'md' | 'lg';
114
- icon?: string;
115
- disabled?: boolean;
116
- }
117
-
118
- export interface UIDatePicker extends HTMLElement {
119
- format: string;
120
- value: string;
121
- min?: string;
122
- max?: string;
123
- }
124
-
125
- // Add other component interfaces as needed
126
- }
127
-
128
- declare global {
129
- interface HTMLElementTagNameMap {
130
- 'ui-button': import('@diniz/webcomponents').UIButton;
131
- 'ui-date-picker': import('@diniz/webcomponents').UIDatePicker;
132
- // Add other components as needed
133
- }
134
- }
100
+ document.querySelector('ui-input')?.addEventListener('input', (e: Event) => {
101
+ const input = e.target as HTMLInputElement;
102
+ console.log('Input value:', input.value);
103
+ });
135
104
  ```
136
105
 
137
- ### 7. Build for Production
106
+ ### 4. Run the development server
138
107
 
139
108
  ```bash
140
- npm run build
109
+ npm run dev
141
110
  ```
142
111
 
143
- The build output will be in the `dist` folder, ready to deploy to any static hosting service.
112
+ Your app is now running with web components! Open your browser and start building.
144
113
 
145
- ### Tree-shaking (Import Only What You Need)
114
+ ### Example: Building a Counter with Signals
146
115
 
147
- You can import individual components to reduce bundle size:
116
+ Create reactive components using the signals system:
148
117
 
118
+ **src/components/counter.ts**
149
119
  ```typescript
150
- // Import only specific components
151
- import { UIButton } from '@diniz/webcomponents';
152
- import '@diniz/webcomponents/dist/style.css';
120
+ import { BaseComponent } from '@diniz/webcomponents';
153
121
 
154
- // The component is automatically registered
155
- // Now you can use <ui-button> in your HTML
156
- ```
157
-
158
- ### Configuration Tips
122
+ class CounterComponent extends BaseComponent {
123
+ private count = this.useSignal(0);
124
+
125
+ connectedCallback() {
126
+ super.connectedCallback();
127
+ this.render();
128
+ }
159
129
 
160
- **Vite Config** - No special configuration needed! Web Components work out of the box with Vite.
130
+ private increment() {
131
+ this.count.set(this.count.get() + 1);
132
+ }
161
133
 
162
- **CSS Customization** - Override CSS custom properties to match your theme:
134
+ private decrement() {
135
+ this.count.set(this.count.get() - 1);
136
+ }
163
137
 
164
- ```css
165
- :root {
166
- --color-primary: #3b82f6;
167
- --color-secondary: #8b5cf6;
168
- --color-success: #10b981;
169
- --color-danger: #ef4444;
170
- --color-warning: #f59e0b;
171
- --color-info: #06b6d4;
172
-
173
- --radius-sm: 0.25rem;
174
- --radius-md: 0.375rem;
175
- --radius-lg: 0.5rem;
138
+ render() {
139
+ this.shadowRoot!.innerHTML = `
140
+ <style>
141
+ :host {
142
+ display: block;
143
+ padding: 2rem;
144
+ text-align: center;
145
+ }
146
+ .count {
147
+ font-size: 3rem;
148
+ margin: 1rem 0;
149
+ color: var(--color-primary, #24ec71);
150
+ }
151
+ .buttons {
152
+ display: flex;
153
+ gap: 1rem;
154
+ justify-content: center;
155
+ }
156
+ </style>
157
+
158
+ <div>
159
+ <h2>Counter</h2>
160
+ <div class="count">${this.count.get()}</div>
161
+ <div class="buttons">
162
+ <ui-button id="decrement" variant="secondary">-</ui-button>
163
+ <ui-button id="increment" variant="primary">+</ui-button>
164
+ </div>
165
+ </div>
166
+ `;
167
+
168
+ this.shadowRoot!.getElementById('increment')?.addEventListener('click',
169
+ () => this.increment()
170
+ );
171
+ this.shadowRoot!.getElementById('decrement')?.addEventListener('click',
172
+ () => this.decrement()
173
+ );
174
+ }
176
175
  }
176
+
177
+ customElements.define('my-counter', CounterComponent);
177
178
  ```
178
- **Update `src/main.ts`:**
179
+
180
+ **src/main.ts**
179
181
  ```typescript
180
182
  import '@diniz/webcomponents';
181
183
  import '@diniz/webcomponents/dist/style.css';
184
+ import './components/counter';
185
+ import './style.css';
182
186
 
183
187
  document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
184
188
  <div>
185
189
  <h1>My Web Components App</h1>
186
- <ui-button variant="primary">Click Me</ui-button>
187
- <ui-date-picker format="DD/MM/YYYY"></ui-date-picker>
188
- <ui-table id="myTable"></ui-table>
190
+ <my-counter></my-counter>
189
191
  </div>
190
192
  `;
191
-
192
- // Add some data to the table
193
- const table = document.getElementById('myTable') as any;
194
- table.data = {
195
- columns: [
196
- { key: 'name', label: 'Name' },
197
- { key: 'role', label: 'Role' }
198
- ],
199
- rows: [
200
- { name: 'Alice', role: 'Admin' },
201
- { name: 'Bob', role: 'User' }
202
- ]
203
- };
204
- ```
205
-
206
- ### Using via CDN or Direct Import
207
-
208
- ```html
209
- <script type="module">
210
- import '@diniz/webcomponents';
211
- </script>
212
-
213
- <ui-button variant="primary">Click Me</ui-button>
214
- <ui-date-picker format="DD/MM/YYYY"></ui-date-picker>
215
- <ui-table></ui-table>
216
193
  ```
217
194
 
218
- ## Components
219
-
220
- ### 🔘 Button (`ui-button`)
221
-
222
- A versatile button component with multiple variants, sizes, and icon support.
195
+ ### Adding Routing to Your App
223
196
 
224
- **Features:**
225
- - 3 variants: `primary`, `secondary`, `ghost`
226
- - 3 sizes: `sm`, `md`, `lg`
227
- - Icon support with [Feather Icons](https://feathericons.com/)
228
- - Icon positioning (left/right)
229
- - Icon-only buttons
230
- - Disabled state support
231
- - Button type support
232
- - Smooth transitions and hover effects
197
+ The library includes a built-in router for client-side navigation. Here's how to set it up:
233
198
 
234
- **Usage:**
235
- ```html
236
- <ui-button variant="primary" size="md">Primary Button</ui-button>
237
- <ui-button variant="secondary" size="sm">Secondary</ui-button>
238
- <ui-button variant="ghost" disabled>Disabled</ui-button>
239
-
240
- <!-- With icons -->
241
- <ui-button variant="primary" icon="check">Save</ui-button>
242
- <ui-button variant="secondary" icon="trash-2" icon-position="right">Delete</ui-button>
243
- <ui-button variant="ghost" icon="settings"></ui-button>
244
- ```
199
+ #### 1. Create your route configuration
245
200
 
246
- **Attributes:**
247
- - `variant` - Button style (`primary` | `secondary` | `ghost`)
248
- - `size` - Button size (`sm` | `md` | `lg`)
249
- - `icon` - Icon name from Feather Icons
250
- - `icon-position` - Icon position (`left` | `right`, default: `left`)
251
- - `disabled` - Disable the button
252
- - `type` - Button type (`button` | `submit` | `reset`)
253
-
254
- ---
255
-
256
- ### 📅 Date Picker (`ui-date-picker`)
201
+ **src/router.ts**
202
+ ```typescript
203
+ import { createRouter, type Route } from '@diniz/webcomponents';
257
204
 
258
- A customizable date picker with multiple format options and calendar support.
205
+ export const routes: Route[] = [
206
+ {
207
+ path: '/',
208
+ load: () => import('./pages/home'),
209
+ component: 'home-page'
210
+ },
211
+ {
212
+ path: '/about',
213
+ load: () => import('./pages/about'),
214
+ component: 'about-page'
215
+ },
216
+ {
217
+ path: '/counter',
218
+ load: () => import('./components/counter'),
219
+ component: 'my-counter'
220
+ }
221
+ ];
259
222
 
260
- **Features:**
261
- - 5 date formats: `YYYY-MM-DD`, `DD/MM/YYYY`, `MM/DD/YYYY`, `DD-MM-YYYY`, `MM-DD-YYYY`
262
- - Native calendar picker integration
263
- - Min/max date constraints
264
- - Text input with format validation
265
- - Real-time format conversion
266
- - Disabled state support
267
- - Custom events for date changes
223
+ // Initialize the router with your routes
224
+ // The router automatically sets up navigation and loads the initial route
225
+ createRouter(routes);
268
226
 
269
- **Usage:**
270
- ```html
271
- <ui-date-picker
272
- format="DD/MM/YYYY"
273
- value="2026-02-26"
274
- min="2026-01-01"
275
- max="2026-12-31"
276
- ></ui-date-picker>
277
-
278
- <script>
279
- const picker = document.querySelector('ui-date-picker');
280
-
281
- picker.addEventListener('date-change', (e) => {
282
- console.log('ISO:', e.detail.value);
283
- console.log('Formatted:', e.detail.formattedValue);
284
- });
285
- </script>
227
+ // Optional: specify a custom app container selector (default is '#app')
228
+ // createRouter(routes, '#my-app-container');
286
229
  ```
287
230
 
288
- **Attributes:**
289
- - `format` - Date display format
290
- - `value` - Date value in ISO format (YYYY-MM-DD)
291
- - `min` - Minimum date (ISO format)
292
- - `max` - Maximum date (ISO format)
293
- - `disabled` - Disable the picker
294
- - `placeholder` - Placeholder text
295
-
296
- **Methods:**
297
- - `getISOValue()` - Get date in ISO format
298
- - `getFormattedValue()` - Get date in display format
299
- - `setValue(isoDate)` - Set the date value
300
- - `clear()` - Clear the date
231
+ > **How it works:** The `createRouter()` function sets up the routing system, registers event listeners for navigation, and automatically loads the initial route when the page loads. Navigation happens via links with the `data-link` attribute, and the browser's back/forward buttons work automatically.
301
232
 
302
- **Events:**
303
- - `date-change` - Fired when date changes
304
- - `date-input` - Fired during input
233
+ **Optional: Adding Route Guards**
305
234
 
306
- ---
235
+ You can protect routes with guard functions that return a boolean or a Promise:
307
236
 
308
- ### 📋 Table (`ui-table`)
237
+ ```typescript
238
+ import { createRouter, type Route } from '@diniz/webcomponents';
309
239
 
310
- A dynamic data table with customizable columns and alignment.
240
+ // Example: Synchronous guard
241
+ const isAuthenticated = () => {
242
+ return localStorage.getItem('user') !== null;
243
+ };
311
244
 
312
- **Features:**
313
- - Dynamic column configuration
314
- - Text alignment per column (left, center, right)
315
- - Responsive layout
316
- - Automatic row rendering
317
- - Theme-aware styling
245
+ // Example: Async guard (e.g., checking with an API)
246
+ const hasPermission = async () => {
247
+ const response = await fetch('/api/check-permission');
248
+ const data = await response.json();
249
+ return data.hasAccess;
250
+ };
318
251
 
319
- **Usage:**
320
- ```html
321
- <ui-table id="myTable"></ui-table>
252
+ export const routes: Route[] = [
253
+ {
254
+ path: '/',
255
+ load: () => import('./pages/home'),
256
+ component: 'home-page'
257
+ },
258
+ {
259
+ path: '/profile',
260
+ load: () => import('./pages/profile'),
261
+ component: 'profile-page',
262
+ guard: isAuthenticated // Redirect to home if guard returns false
263
+ },
264
+ {
265
+ path: '/admin',
266
+ load: () => import('./pages/admin'),
267
+ component: 'admin-page',
268
+ guard: hasPermission // Supports async guards
269
+ }
270
+ ];
322
271
 
323
- <script type="module">
324
- const table = document.getElementById('myTable');
325
-
326
- table.data = {
327
- columns: [
328
- { key: 'name', label: 'Name' },
329
- { key: 'role', label: 'Role' },
330
- { key: 'score', label: 'Score', align: 'right' }
331
- ],
332
- rows: [
333
- { name: 'Alice', role: 'Admin', score: 95 },
334
- { name: 'Bob', role: 'User', score: 87 }
335
- ]
336
- };
337
- </script>
272
+ createRouter(routes);
338
273
  ```
339
274
 
340
- **Properties:**
341
- - `data` - Object with `columns` and `rows`
342
- - `columns`: Array of `{ key, label, align? }`
343
- - `rows`: Array of objects matching column keys
275
+ #### 2. Create page components (with optional shared navigation)
344
276
 
345
- ---
277
+ You can create a reusable navigation component:
346
278
 
347
- ### 📄 Pagination (`ui-pagination`)
279
+ **src/components/nav.ts**
280
+ ```typescript
281
+ import { BaseComponent } from '@diniz/webcomponents';
348
282
 
349
- Smart pagination component with ellipsis for large page counts.
283
+ class NavComponent extends BaseComponent {
284
+ connectedCallback() {
285
+ super.connectedCallback();
286
+ this.render();
287
+ }
350
288
 
351
- **Features:**
352
- - Automatic page number generation
353
- - Smart ellipsis for large page counts
354
- - Previous/Next navigation
355
- - "Showing X to Y of Z" info display
356
- - Disabled states for edge pages
357
- - Custom events for page changes
358
- - ARIA labels for accessibility
289
+ render() {
290
+ this.shadowRoot!.innerHTML = `
291
+ <style>
292
+ nav {
293
+ background: var(--color-surface, #1e1e1e);
294
+ padding: 1rem;
295
+ display: flex;
296
+ gap: 1rem;
297
+ margin-bottom: 2rem;
298
+ }
299
+ nav a {
300
+ color: var(--color-text, #fff);
301
+ text-decoration: none;
302
+ padding: 0.5rem 1rem;
303
+ border-radius: 0.25rem;
304
+ }
305
+ nav a:hover {
306
+ background: var(--color-surface-hover, #2a2a2a);
307
+ }
308
+ </style>
309
+
310
+ <nav>
311
+ <a href="/" data-link>Home</a>
312
+ <a href="/about" data-link>About</a>
313
+ <a href="/counter" data-link>Counter</a>
314
+ </nav>
315
+ `;
316
+ }
317
+ }
359
318
 
360
- **Usage:**
361
- ```html
362
- <ui-pagination
363
- total="250"
364
- current-page="5"
365
- page-size="10"
366
- ></ui-pagination>
367
-
368
- <script>
369
- const pagination = document.querySelector('ui-pagination');
370
-
371
- pagination.addEventListener('page-change', (e) => {
372
- console.log('Page:', e.detail.page);
373
- console.log('Total Pages:', e.detail.totalPages);
374
- // Load new data...
375
- });
376
- </script>
319
+ customElements.define('app-nav', NavComponent);
377
320
  ```
378
321
 
379
- **Attributes/Properties:**
380
- - `total` - Total number of items
381
- - `current-page` - Current page number
382
- - `page-size` - Items per page (default: 10)
322
+ **src/pages/home.ts**
323
+ ```typescript
324
+ import { BaseComponent } from '@diniz/webcomponents';
325
+ import '../components/nav';
383
326
 
384
- **Computed Properties:**
385
- - `totalPages` - Total number of pages
327
+ class HomePage extends BaseComponent {
328
+ connectedCallback() {
329
+ super.connectedCallback();
330
+ this.render();
331
+ }
386
332
 
387
- **Events:**
388
- - `page-change` - Fired when page changes, includes pagination details
333
+ render() {
334
+ this.shadowRoot!.innerHTML = `
335
+ <app-nav></app-nav>
336
+ <h1>Home Page</h1>
337
+ <p>Welcome to my web components app!</p>
338
+ <ui-button variant="primary">
339
+ <a href="/counter" data-link style="color: inherit; text-decoration: none;">
340
+ Try Counter
341
+ </a>
342
+ </ui-button>
343
+ `;
344
+ }
345
+ }
389
346
 
390
- ---
347
+ customElements.define('home-page', HomePage);
348
+ ```
391
349
 
392
- ### 📝 Input (`ui-input`)
350
+ **src/pages/about.ts**
351
+ ```typescript
352
+ import { BaseComponent } from '@diniz/webcomponents';
353
+ import '../components/nav';
393
354
 
394
- Advanced form input with built-in validation and error handling.
355
+ class AboutPage extends BaseComponent {
356
+ connectedCallback() {
357
+ super.connectedCallback();
358
+ this.render();
359
+ }
395
360
 
396
- **Features:**
397
- - Multiple input types: `text`, `email`, `password`, `number`, `tel`, `url`
398
- - Built-in validation rules:
399
- - Email domain validation
400
- - Password matching
401
- - Min/max length
402
- - Regex patterns
403
- - Custom validators
404
- - Real-time validation feedback
405
- - Error message display
406
- - Touched state tracking
407
- - Disabled state support
361
+ render() {
362
+ this.shadowRoot!.innerHTML = `
363
+ <app-nav></app-nav>
364
+ <h1>About</h1>
365
+ <p>This is a Vite app using web components with routing.</p>
366
+ `;
367
+ }
368
+ }
408
369
 
409
- **Usage:**
410
- ```html
411
- <ui-input
412
- type="email"
413
- label="Email"
414
- placeholder="you@example.com"
415
- required
416
- validate="emailDomain:company.com"
417
- ></ui-input>
418
-
419
- <ui-input
420
- type="password"
421
- label="Password"
422
- minlength="8"
423
- required
424
- ></ui-input>
370
+ customElements.define('about-page', AboutPage);
425
371
  ```
426
372
 
427
- **Attributes:**
428
- - `type` - Input type
429
- - `label` - Label text
430
- - `placeholder` - Placeholder text
431
- - `required` - Required field
432
- - `pattern` - Regex pattern
433
- - `minlength` / `maxlength` - Length constraints
434
- - `min` / `max` - Number constraints
435
- - `error-message` - Custom error message
436
- - `disabled` - Disable input
437
- - `name` - Form field name
438
- - `validate` - Validation rule (e.g., `emailDomain:company.com`)
439
-
440
- **State:**
441
- - `value` - Current input value
442
- - `valid` - Validation state
443
- - `touched` - Whether field has been interacted with
444
- - `error` - Current error message
445
-
446
- ---
447
-
448
- ### 🪟 Modal (`ui-modal`)
449
-
450
- Responsive modal dialog with customizable sizes and behaviors.
451
-
452
- **Features:**
453
- - 5 size options: `sm`, `md`, `lg`, `xl`, `full`
454
- - Auto-close on Escape key (configurable)
455
- - Auto-close on backdrop click (configurable)
456
- - Smooth animations (fade in, slide up)
457
- - Header, body, and footer slots
458
- - Programmatic open/close API
459
- - Custom events
460
- - Body scroll lock when open
461
-
462
- **Usage:**
463
- ```html
464
- <ui-button id="openModal">Open Modal</ui-button>
465
-
466
- <ui-modal id="myModal" title="Welcome!" size="md">
467
- <p>This is the modal content.</p>
468
- <p>You can include any HTML here.</p>
469
-
470
- <div slot="footer">
471
- <ui-button id="closeBtn" variant="secondary">Cancel</ui-button>
472
- <ui-button id="confirmBtn" variant="primary">Confirm</ui-button>
473
- </div>
474
- </ui-modal>
373
+ #### 3. Initialize the router in main.ts
475
374
 
476
- <script>
477
- const modal = document.getElementById('myModal');
478
- const openBtn = document.getElementById('openModal');
479
- const closeBtn = document.getElementById('closeBtn');
480
-
481
- openBtn.addEventListener('click', () => modal.open());
482
- closeBtn.addEventListener('click', () => modal.close());
483
-
484
- modal.addEventListener('modal-close', () => {
485
- console.log('Modal closed');
486
- });
487
- </script>
375
+ **src/main.ts**
376
+ ```typescript
377
+ import '@diniz/webcomponents';
378
+ import '@diniz/webcomponents/dist/style.css';
379
+ import './style.css';
380
+ import './router'; // This loads the routes and initializes routing
488
381
  ```
489
382
 
490
- **Attributes:**
491
- - `title` - Modal title text
492
- - `size` - Modal size (`sm` | `md` | `lg` | `xl` | `full`)
493
- - `open` - Open state attribute
494
- - `no-close-on-escape` - Disable closing on Escape key
495
- - `no-close-on-backdrop` - Disable closing on backdrop click
496
-
497
- **Methods:**
498
- - `open()` - Open the modal
499
- - `close()` - Close the modal
383
+ The moment you import `./router`, the routing system:
384
+ 1. Registers all event listeners for navigation
385
+ 2. Automatically loads the initial route based on the current URL
386
+ 3. Starts handling clicks on `[data-link]` elements
387
+ 4. Enables browser back/forward button support
500
388
 
501
- **Events:**
502
- - `modal-open` - Fired when modal opens
503
- - `modal-close` - Fired when modal closes
389
+ That's it! Your app now has client-side routing with:
390
+ - Lazy-loaded pages
391
+ - Browser back/forward navigation
392
+ - ✅ Declarative routing with `data-link` attribute
393
+ - ✅ Optional route guards for protected pages
394
+ - ✅ Reusable navigation component
504
395
 
505
- ---
396
+ ## Components
506
397
 
507
- ### 📋 Select (`ui-select`)
398
+ - **ui-button** - Button with variants, sizes, icons
399
+ - **ui-input** - Input with validation
400
+ - **ui-table** - Data table with actions
401
+ - **ui-date-picker** - Date picker
402
+ - **ui-pagination** - Pagination control
403
+ - **ui-select** - Dropdown selection
404
+ - **ui-checkbox** - Checkbox input
405
+ - **ui-modal** - Modal dialog
406
+ - **ui-card** - Card container
407
+ - **ui-tabs** - Tab navigation
408
+ - **ui-stepper** - Step indicator
409
+ - **ui-toast** - Toast notifications
410
+ - **ui-upload** - File upload
411
+ - **ui-layout** - Application layout
412
+
413
+ For detailed documentation on each component, see the demo implementations in `src/features/`.
508
414
 
509
- Customizable dropdown select with search capability.
415
+ ## Core Features
510
416
 
511
- **Features:**
512
- - JSON-based options configuration
513
- - Searchable dropdown (optional)
514
- - Keyboard navigation
515
- - Disabled options support
516
- - Custom placeholder text
517
- - Change events with full option details
518
- - Click-outside to close
519
- - Smooth animations
520
- - Theme-aware styling
417
+ ### Signals & Reactivity
521
418
 
522
- **Usage:**
523
- ```html
524
- <ui-select
525
- id="mySelect"
526
- label="Choose a Country"
527
- placeholder="Select country..."
528
- searchable
529
- ></ui-select>
530
-
531
- <script>
532
- const select = document.getElementById('mySelect');
533
-
534
- // Set options
535
- const options = [
536
- { value: 'us', label: 'United States' },
537
- { value: 'uk', label: 'United Kingdom' },
538
- { value: 'ca', label: 'Canada' },
539
- { value: 'au', label: 'Australia', disabled: true }
540
- ];
541
-
542
- select.setAttribute('options', JSON.stringify(options));
543
-
544
- // Set initial value
545
- select.setAttribute('value', 'us');
546
-
547
- // Listen for changes
548
- select.addEventListener('select-change', (e) => {
549
- console.log('Value:', e.detail.value);
550
- console.log('Option:', e.detail.option);
551
- });
552
- </script>
553
- ```
419
+ Create reactive, auto-updating UI with signals. Changes automatically trigger re-renders:
554
420
 
555
- **Attributes:**
556
- - `label` - Label text above select
557
- - `placeholder` - Placeholder when no selection
558
- - `options` - JSON string of options array
559
- - `value` - Currently selected value
560
- - `disabled` - Disable the select
561
- - `searchable` - Enable search functionality
562
-
563
- **Option Format:**
564
421
  ```typescript
565
- {
566
- value: string; // The option value
567
- label: string; // Display text
568
- disabled?: boolean; // Optional: disable option
569
- }
570
- ```
422
+ import { BaseComponent } from '@diniz/webcomponents';
571
423
 
572
- **Events:**
573
- - `select-change` - Fired when selection changes
574
- - `detail.value` - Selected value
575
- - `detail.option` - Full option object
424
+ class CounterComponent extends BaseComponent {
425
+ // Create a reactive signal with initial value 0
426
+ private count = this.useSignal(0);
427
+
428
+ connectedCallback() {
429
+ super.connectedCallback();
430
+ this.render();
431
+ }
576
432
 
577
- ---
433
+ private increment() {
434
+ // Update signal value - automatically triggers re-render
435
+ this.count.set(this.count.get() + 1);
436
+ }
578
437
 
579
- ### ☑️ Checkbox (`ui-checkbox`)
438
+ private reset() {
439
+ this.count.set(0);
440
+ }
580
441
 
581
- Flexible checkbox with indeterminate state support.
442
+ render() {
443
+ const currentCount = this.count.get();
444
+
445
+ this.shadowRoot!.innerHTML = `
446
+ <style>
447
+ :host {
448
+ display: flex;
449
+ flex-direction: column;
450
+ gap: 1rem;
451
+ padding: 2rem;
452
+ }
453
+ .count-display {
454
+ font-size: 2rem;
455
+ font-weight: bold;
456
+ color: var(--color-primary, #24ec71);
457
+ }
458
+ </style>
459
+
460
+ <div class="count-display">Count: ${currentCount}</div>
461
+ <button id="increment">Increment</button>
462
+ <button id="reset">Reset</button>
463
+ `;
464
+
465
+ // Connect event listeners to update signals
466
+ this.shadowRoot!.getElementById('increment')?.addEventListener('click',
467
+ () => this.increment()
468
+ );
469
+ this.shadowRoot!.getElementById('reset')?.addEventListener('click',
470
+ () => this.reset()
471
+ );
472
+ }
473
+ }
582
474
 
583
- **Features:**
584
- - 3 sizes: `sm`, `md`, `lg`
585
- - Checked/unchecked states
586
- - Indeterminate state (useful for "select all")
587
- - Disabled state
588
- - Label support (attribute or slot)
589
- - Programmatic API
590
- - Custom events
591
- - Smooth animations and transitions
592
- - Theme-aware styling
475
+ customElements.define('counter-app', CounterComponent);
476
+ ```
593
477
 
594
- **Usage:**
478
+ **Usage in HTML:**
595
479
  ```html
596
- <!-- Basic usage -->
597
- <ui-checkbox label="Accept terms"></ui-checkbox>
598
- <ui-checkbox label="Subscribe" checked></ui-checkbox>
599
- <ui-checkbox label="Disabled" disabled></ui-checkbox>
600
-
601
- <!-- With sizes -->
602
- <ui-checkbox label="Small" size="sm"></ui-checkbox>
603
- <ui-checkbox label="Medium" size="md"></ui-checkbox>
604
- <ui-checkbox label="Large" size="lg"></ui-checkbox>
605
-
606
- <!-- Programmatic usage -->
607
- <ui-checkbox id="myCheckbox" label="Select All"></ui-checkbox>
608
-
609
- <script>
610
- const checkbox = document.getElementById('myCheckbox');
611
-
612
- // Listen for changes
613
- checkbox.addEventListener('checkbox-change', (e) => {
614
- console.log('Checked:', e.detail.checked);
615
- });
616
-
617
- // Set states programmatically
618
- checkbox.setChecked(true);
619
- checkbox.setIndeterminate(true);
620
- </script>
480
+ <counter-app></counter-app>
621
481
  ```
622
482
 
623
- **Attributes:**
624
- - `label` - Label text
625
- - `checked` - Checked state
626
- - `indeterminate` - Indeterminate state
627
- - `disabled` - Disable checkbox
628
- - `size` - Checkbox size (`sm` | `md` | `lg`)
483
+ **Key features:**
484
+ - `this.useSignal(value)` - Create a reactive signal
485
+ - `signal.get()` - Read the current value
486
+ - `signal.set(newValue)` - Update value and trigger re-render
487
+ - `this.setState({ ... })` - Update multiple values at once
488
+ - Changes are isolated within component's shadow DOM
629
489
 
630
- **Methods:**
631
- - `setChecked(checked: boolean)` - Set checked state
632
- - `setIndeterminate(indeterminate: boolean)` - Set indeterminate state
490
+ ### Signals with Separated HTML & TypeScript Files
633
491
 
634
- **Events:**
635
- - `checkbox-change` - Fired when state changes
636
- - `detail.checked` - New checked state
492
+ When using separate HTML template files, you can achieve automatic reactivity so the HTML updates whenever a signal changes:
637
493
 
638
- ---
494
+ **counter.html**
495
+ ```html
496
+ <style>
497
+ :host {
498
+ display: flex;
499
+ flex-direction: column;
500
+ gap: 1rem;
501
+ padding: 2rem;
502
+ }
503
+ .count-display {
504
+ font-size: 2rem;
505
+ font-weight: bold;
506
+ color: var(--color-primary, #24ec71);
507
+ }
508
+ </style>
639
509
 
640
- ### 🎯 Sidebar (`app-sidebar`)
510
+ <div class="count-display">
511
+ Count: <span id="countValue">0</span>
512
+ </div>
513
+ <button id="incrementBtn">Increment</button>
514
+ <button id="resetBtn">Reset</button>
515
+ ```
641
516
 
642
- Navigation sidebar component with links.
517
+ **counter.ts - Automatic Reactivity Approach**
518
+ ```typescript
519
+ import { BaseComponent } from '@diniz/webcomponents';
520
+ import template from './counter.html?raw';
643
521
 
644
- **Features:**
645
- - Workspace navigation
646
- - Active link highlighting (via routing)
647
- - Theme-aware styling
522
+ class CounterComponent extends BaseComponent {
523
+ private count = this.useSignal(0);
524
+
525
+ connectedCallback() {
526
+ super.connectedCallback();
527
+ this.render();
528
+ this.setupEventListeners();
529
+
530
+ // Subscribe to signal changes - re-render when count changes
531
+ this.watchSignal(this.count, () => {
532
+ this.updateCountDisplay();
533
+ });
534
+ }
648
535
 
649
- **Usage:**
650
- ```html
651
- <app-sidebar></app-sidebar>
652
- ```
536
+ private increment() {
537
+ this.count.set(this.count.get() + 1);
538
+ }
653
539
 
654
- ---
540
+ private reset() {
541
+ this.count.set(0);
542
+ }
655
543
 
656
- ### 📐 Layout (`app-layout`)
544
+ // Single method that updates the display - called whenever signal changes
545
+ private updateCountDisplay() {
546
+ const countValue = this.shadowRoot?.getElementById('countValue');
547
+ if (countValue) {
548
+ countValue.textContent = String(this.count.get());
549
+ }
550
+ }
657
551
 
658
- Application layout wrapper with navigation and sidebar.
552
+ private setupEventListeners() {
553
+ this.shadowRoot?.getElementById('incrementBtn')?.addEventListener('click',
554
+ () => this.increment()
555
+ );
556
+ this.shadowRoot?.getElementById('resetBtn')?.addEventListener('click',
557
+ () => this.reset()
558
+ );
559
+ }
659
560
 
660
- **Features:**
661
- - Top navigation bar
662
- - Sidebar integration
663
- - Main content area with slot
664
- - Responsive layout
561
+ render() {
562
+ this.shadowRoot!.innerHTML = template;
563
+ this.updateCountDisplay(); // Initial display
564
+ }
565
+ }
665
566
 
666
- **Usage:**
667
- ```html
668
- <app-layout>
669
- <your-page-component></your-page-component>
670
- </app-layout>
567
+ customElements.define('counter-app', CounterComponent);
671
568
  ```
672
569
 
673
- ---
674
-
675
- ## Core Features
676
-
677
- ### Base Component
570
+ **Alternative: Full Re-render on Signal Change**
678
571
 
679
- All components extend `BaseComponent` which provides:
572
+ For simpler components, you can also re-render the entire template when any signal changes:
680
573
 
681
- **Signal-based Reactivity:**
682
574
  ```typescript
683
- class MyComponent extends BaseComponent {
575
+ class CounterComponent extends BaseComponent {
684
576
  private count = this.useSignal(0);
685
577
 
686
578
  connectedCallback() {
687
579
  super.connectedCallback();
688
- // count.set() automatically triggers re-render
689
- this.count.set(this.count.get() + 1);
580
+ this.render();
581
+ this.setupEventListeners();
582
+ // Re-render entire component when signal changes
583
+ this.watchSignal(this.count, () => this.render());
690
584
  }
691
- }
692
- ```
693
585
 
694
- **State Management:**
695
- ```typescript
696
- class MyComponent extends BaseComponent<{ user: string }> {
697
- constructor() {
698
- super();
699
- this.state = { user: '' };
700
- }
701
-
702
- updateUser() {
703
- this.setState({ user: 'Alice' }); // Triggers re-render
586
+ private increment() {
587
+ this.count.set(this.count.get() + 1);
588
+ // No need to manually update - render() is called automatically
704
589
  }
705
- }
706
- ```
707
590
 
708
- ### Router
591
+ private reset() {
592
+ this.count.set(0);
593
+ }
709
594
 
710
- Built-in client-side router with layouts:
595
+ private setupEventListeners() {
596
+ // Re-attach listeners after each render
597
+ this.shadowRoot?.getElementById('incrementBtn')?.addEventListener('click',
598
+ () => this.increment()
599
+ );
600
+ this.shadowRoot?.getElementById('resetBtn')?.addEventListener('click',
601
+ () => this.reset()
602
+ );
603
+ }
711
604
 
712
- ```typescript
713
- const routes = [
714
- {
715
- path: '/',
716
- layout: 'app-layout',
717
- load: () => import('./features/home/home-page'),
718
- component: 'home-page'
605
+ render() {
606
+ const currentCount = this.count.get();
607
+
608
+ this.shadowRoot!.innerHTML = `
609
+ <style>
610
+ :host {
611
+ display: flex;
612
+ flex-direction: column;
613
+ gap: 1rem;
614
+ padding: 2rem;
615
+ }
616
+ .count-display {
617
+ font-size: 2rem;
618
+ font-weight: bold;
619
+ color: var(--color-primary, #24ec71);
620
+ }
621
+ </style>
622
+
623
+ <div class="count-display">Count: ${currentCount}</div>
624
+ <button id="incrementBtn">Increment</button>
625
+ <button id="resetBtn">Reset</button>
626
+ `;
627
+
628
+ this.setupEventListeners();
719
629
  }
720
- ];
630
+ }
631
+
632
+ customElements.define('counter-app', CounterComponent);
721
633
  ```
722
634
 
723
- ### Store
635
+ **Key improvements:**
636
+ - Use `this.watchSignal(signal, callback)` to subscribe to signal changes
637
+ - When signal updates, callback triggers automatically - no manual DOM updates needed
638
+ - Choose between:
639
+ - **Selective updates** - Only update specific DOM elements (better performance)
640
+ - **Full re-render** - Re-render entire template (simpler logic, less efficient)
641
+ - The HTML automatically stays in sync with signal values
724
642
 
725
- Global state management:
643
+ ### Enhanced: useSignalHtml for Direct DOM Binding
726
644
 
727
- ```typescript
728
- import { store } from './core/store';
645
+ For even cleaner code, use `useSignalHtml()` to create a signal that automatically updates a specific HTML element:
729
646
 
730
- store.setState({ theme: 'dark' });
731
- const currentState = store.getState();
647
+ **counter.html**
648
+ ```html
649
+ <style>
650
+ :host {
651
+ display: flex;
652
+ flex-direction: column;
653
+ gap: 1rem;
654
+ padding: 2rem;
655
+ }
656
+ .count-display {
657
+ font-size: 2rem;
658
+ font-weight: bold;
659
+ color: var(--color-primary, #24ec71);
660
+ }
661
+ </style>
732
662
 
733
- store.subscribe(state => {
734
- console.log('State changed:', state);
735
- });
663
+ <div class="count-display">
664
+ Count: <span id="countValue">0</span>
665
+ </div>
666
+ <button id="incrementBtn">Increment</button>
667
+ <button id="resetBtn">Reset</button>
736
668
  ```
737
669
 
738
- ### HTTP Client
739
-
740
- Lightweight HTTP client with interceptor support for API requests:
741
-
670
+ **counter.ts - Simplified with useSignalHtml**
742
671
  ```typescript
743
- import { http } from '@diniz/webcomponents';
672
+ import { BaseComponent } from '@diniz/webcomponents';
673
+ import template from './counter.html?raw';
744
674
 
745
- // Set base URL for all requests
746
- http.setBaseURL('https://api.example.com');
747
-
748
- // Set default headers
749
- http.setDefaultHeaders({ 'Authorization': 'Bearer token' });
675
+ class CounterComponent extends BaseComponent {
676
+ // Create signal bound to HTML element with ID 'countValue'
677
+ // Automatically updates the element's textContent when signal changes
678
+ private count = this.useSignalHtml('countValue', 0);
679
+
680
+ connectedCallback() {
681
+ super.connectedCallback();
682
+ this.render();
683
+ this.setupEventListeners();
684
+ }
750
685
 
751
- // Make requests
752
- const users = await http.get<User[]>('/users');
753
- const newUser = await http.post<User>('/users', { name: 'Alice' });
754
- await http.put(`/users/${id}`, updatedData);
755
- await http.delete(`/users/${id}`);
756
- ```
686
+ private increment() {
687
+ // Just update the signal - HTML updates automatically
688
+ this.count.set(this.count.get() + 1);
689
+ }
757
690
 
758
- **Request Interceptors:**
691
+ private reset() {
692
+ // Just update the signal - HTML updates automatically
693
+ this.count.set(0);
694
+ }
759
695
 
760
- ```typescript
761
- // Add auth token to every request
762
- http.interceptors.request.use((config) => {
763
- const token = localStorage.getItem('auth_token');
764
- if (token) {
765
- config.headers = config.headers || {};
766
- config.headers['Authorization'] = `Bearer ${token}`;
767
- }
768
- return config;
769
- });
696
+ private setupEventListeners() {
697
+ this.shadowRoot?.getElementById('incrementBtn')?.addEventListener('click',
698
+ () => this.increment()
699
+ );
700
+ this.shadowRoot?.getElementById('resetBtn')?.addEventListener('click',
701
+ () => this.reset()
702
+ );
703
+ }
770
704
 
771
- // Handle request errors
772
- http.interceptors.request.use(
773
- (config) => config,
774
- (error) => {
775
- console.error('Request failed:', error);
776
- throw error;
705
+ render() {
706
+ this.shadowRoot!.innerHTML = template;
777
707
  }
778
- );
708
+ }
709
+
710
+ customElements.define('counter-app', CounterComponent);
779
711
  ```
780
712
 
781
- **Response Interceptors:**
713
+ **Benefits of `useSignalHtml()`:**
714
+ - No need for `watchSignal()` subscription
715
+ - No need for manual `updateDisplay()` functions
716
+ - Automatically syncs signal value to element's `textContent`
717
+ - One line instead of multiple lines of setup
718
+ - Perfect for data binding in separated HTML/TS architecture
782
719
 
720
+ **Signature:**
783
721
  ```typescript
784
- // Transform response data
785
- http.interceptors.response.use((response) => {
786
- // Unwrap API response if it's nested
787
- if (response.data?.result) {
788
- response.data = response.data.result;
789
- }
790
- return response;
791
- });
792
-
793
- // Handle errors globally
794
- http.interceptors.response.use(
795
- (response) => response,
796
- (error) => {
797
- if (error.response?.status === 401) {
798
- // Handle unauthorized
799
- localStorage.removeItem('auth_token');
800
- window.location.href = '/login';
801
- }
802
- throw error;
803
- }
804
- );
722
+ useSignalHtml<T>(elementId: string, initialValue: T): Signal<T>
805
723
  ```
806
724
 
807
- **Methods:**
725
+ Returns a signal that automatically updates the specified HTML element whenever the value changes.
808
726
 
809
- - `get<T>(url, config?)` - GET request
810
- - `post<T>(url, data?, config?)` - POST request
811
- - `put<T>(url, data?, config?)` - PUT request
812
- - `patch<T>(url, data?, config?)` - PATCH request
813
- - `delete<T>(url, config?)` - DELETE request
814
- - `head<T>(url, config?)` - HEAD request
727
+ This pattern is used throughout the demo components in `src/features/`.
815
728
 
816
- **Configuration:**
729
+ ### HTTP Client
817
730
 
818
731
  ```typescript
819
- interface RequestConfig {
820
- method?: string;
821
- headers?: Record<string, string>;
822
- body?: string | FormData | null;
823
- timeout?: number; // Default: 30000ms
824
- }
825
- ```
732
+ import { http } from '@diniz/webcomponents';
826
733
 
827
- **Features:**
734
+ const users = await http.get<User[]>('/api/users');
735
+ const newUser = await http.post<User>('/api/users', data);
736
+ ```
828
737
 
829
- - Request/response interceptors with error handling
830
- - ✅ Automatic JSON serialization/deserialization
831
- - ✅ Timeout support (default 30s)
832
- - ✅ Global headers and base URL configuration
833
- - ✅ FormData support for file uploads
834
- - ✅ TypeScript generics for type-safe responses
835
- - ✅ Automatic error messages with status codes
738
+ ### Router & Store
836
739
 
837
- ---
740
+ Built-in routing and global state management utilities.
838
741
 
839
742
  ## Theming
840
743
 
841
- All components use CSS custom properties for easy theming:
744
+ Customize colors and spacing using CSS custom properties:
842
745
 
843
746
  ```css
844
747
  :root {
845
748
  --color-primary: #24ec71;
846
- --color-primary-contrast: #ffffff;
847
- --color-ink: #0f172a;
848
- --color-muted: #f1f5f9;
849
- --color-border: #e2e8f0;
749
+ --color-secondary: #8b5cf6;
850
750
  --radius-md: 12px;
851
- --radius-pill: 999px;
852
751
  }
853
752
  ```
854
753
 
855
- ---
856
-
857
- ## Dependencies
858
-
859
- ### Icons
860
-
861
- This library uses **[Feather Icons](https://feathericons.com/)** for beautiful, minimal SVG icons. Feather provides a consistent set of 286 icons perfect for UI components.
862
-
863
- ```typescript
864
- import feather from 'feather-icons';
754
+ ## Development
865
755
 
866
- // Use icons in components
867
- <ui-button icon="plus">Add Item</ui-button>
868
- <ui-button icon="trash-2" variant="danger">Delete</ui-button>
756
+ ```bash
757
+ npm install
758
+ npm run dev # Start dev server
759
+ npm run build # Build for production
760
+ npm run build:lib # Build library distribution
869
761
  ```
870
762
 
871
- [Browse all available Feather icons →](https://feathericons.com/)
872
-
873
- ---
874
-
875
- ## Bundle Size
876
-
877
- @diniz/webcomponents is extremely lightweight with zero runtime dependencies:
878
-
879
- | Package | Size (minified) | Size (gzipped) |
880
- |---------|-----------------|----------------|
881
- | **@diniz/webcomponents** | ~45KB | ~12KB |
882
- | Vue 3 + Router | ~185KB | ~65KB |
883
- | React 18 + Router | ~245KB | ~85KB |
884
- | Angular 15 | ~500KB+ | ~150KB+ |
885
- | Svelte | ~60KB | ~15KB |
886
-
887
- *Sizes are approximate and vary based on included components and tree-shaking effectiveness*
888
-
889
- **Why Web Components?**
890
- - ✅ No framework overhead - use with any framework or vanilla JS
891
- - ✅ Smaller initial bundle size than traditional frameworks
892
- - ✅ Progressive enhancement - works without JavaScript
893
- - ✅ Share components across different projects/frameworks
894
- - ✅ Built-in browser APIs - no external polyfills needed for modern browsers
895
-
896
- ---
897
-
898
763
  ## Browser Support
899
764
 
900
765
  - ✅ Chrome/Edge (latest)
901
766
  - ✅ Firefox (latest)
902
767
  - ✅ Safari (latest)
903
- - ✅ All modern browsers with Custom Elements support
904
-
905
- ---
906
-
907
- ## Development
908
-
909
- ```bash
910
- # Install dependencies
911
- npm install
912
-
913
- # Start dev server
914
- npm run dev
915
-
916
- # Build library
917
- npm run build:lib
918
-
919
- # Build production app
920
- npm run build:prod
921
- ```
922
-
923
- ---
924
-
925
- ## Project Structure
926
-
927
- ```
928
- src/
929
- ├── core/
930
- │ ├── base-component.ts # Base class with signals
931
- │ ├── router.ts # Client-side routing
932
- │ └── store.ts # Global state management
933
- ├── shared/
934
- │ └── components/ # Reusable UI components
935
- │ ├── button.ts
936
- │ ├── checkbox.ts
937
- │ ├── date-picker.ts
938
- │ ├── input.ts
939
- │ ├── modal.ts
940
- │ ├── pagination.ts
941
- │ ├── select.ts
942
- │ └── table.ts
943
- ├── layouts/
944
- │ └── app-layout.ts # Application shell
945
- ├── features/ # Page components
946
- └── styles/
947
- └── theme.css # Global theme variables
948
- ```
949
- ## Contributing
950
-
951
- Contributions are welcome! Please feel free to submit a Pull Request.
952
-
953
- ---
954
768
 
955
769
  ## License
956
770
 
957
771
  MIT © Rodrigo Diniz
958
772
 
959
-