@design.estate/dees-catalog 9.6.0 → 9.6.2

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.
@@ -1,820 +0,0 @@
1
- # UI Components Playbook
2
-
3
- This playbook provides comprehensive guidance for creating and maintaining UI components in the @design.estate/dees-catalog library. Follow these patterns and best practices to ensure consistency, maintainability, and quality.
4
-
5
- ## Table of Contents
6
-
7
- 1. [Component Creation Checklist](#component-creation-checklist)
8
- 2. [Architectural Patterns](#architectural-patterns)
9
- 3. [Component Types and Base Classes](#component-types-and-base-classes)
10
- 4. [Theming System](#theming-system)
11
- 5. [Event Handling](#event-handling)
12
- 6. [State Management](#state-management)
13
- 7. [Form Components](#form-components)
14
- 8. [Overlay Components](#overlay-components)
15
- 9. [Complex Components](#complex-components)
16
- 10. [Performance Optimization](#performance-optimization)
17
- 11. [Focus Management](#focus-management)
18
- 12. [Demo System](#demo-system)
19
- 13. [Common Pitfalls and Anti-patterns](#common-pitfalls-and-anti-patterns)
20
- 14. [Code Examples](#code-examples)
21
-
22
- ## Component Creation Checklist
23
-
24
- When creating a new component, follow this checklist:
25
-
26
- - [ ] Choose the appropriate base class (`DeesElement` or `DeesInputBase`)
27
- - [ ] Use `@customElement('dees-componentname')` decorator
28
- - [ ] Import `themeDefaultStyles` from `00theme.js` and style with `--dees-*` tokens (`cssManager.bdTheme()` is legacy — see [Theming System](#theming-system))
29
- - [ ] Create demo function in separate `.demo.ts` file
30
- - [ ] Export component from `ts_web/elements/index.ts`
31
- - [ ] Use proper TypeScript types and interfaces (prefix with `I` for interfaces, `T` for types)
32
- - [ ] Implement proper event handling with bubbling and composition
33
- - [ ] Consider mobile responsiveness
34
- - [ ] Add focus states for accessibility
35
- - [ ] Clean up resources in `destroy()` method
36
- - [ ] Follow lowercase naming convention for files
37
- - [ ] Add z-index registry support if it's an overlay component
38
-
39
- ## Architectural Patterns
40
-
41
- ### Base Component Structure
42
-
43
- ```typescript
44
- import { customElement, property, state, css, TemplateResult, html, cssManager } from '@design.estate/dees-element';
45
- import { DeesElement } from '@design.estate/dees-element';
46
- import { themeDefaultStyles } from '../00theme.js';
47
- import * as demoFunc from './dees-componentname.demo.js';
48
-
49
- @customElement('dees-componentname')
50
- export class DeesComponentName extends DeesElement {
51
- // Static demo reference
52
- public static demo = demoFunc.demoFunc;
53
-
54
- // Public properties (reactive, can be set via attributes)
55
- @property({ type: String })
56
- public label: string = '';
57
-
58
- @property({ type: Boolean, reflect: true })
59
- public disabled: boolean = false;
60
-
61
- // Internal state (reactive, but not exposed as attributes)
62
- @state()
63
- private internalState: string = '';
64
-
65
- // Static styles with theme support
66
- public static styles = [
67
- themeDefaultStyles,
68
- cssManager.defaultStyles,
69
- css`
70
- :host {
71
- display: block;
72
- background: var(--dees-color-bg-primary);
73
- }
74
- `
75
- ];
76
-
77
- // Render method
78
- public render(): TemplateResult {
79
- return html`
80
- <div class="main-container">
81
- <!-- Component content -->
82
- </div>
83
- `;
84
- }
85
-
86
- // Lifecycle methods
87
- public connectedCallback() {
88
- super.connectedCallback();
89
- // Setup that needs DOM access
90
- }
91
-
92
- public async firstUpdated() {
93
- // One-time initialization after first render
94
- }
95
-
96
- // Cleanup
97
- public destroy() {
98
- // Clean up listeners, observers, registrations
99
- super.destroy();
100
- }
101
- }
102
- ```
103
-
104
- ### Advanced Patterns
105
-
106
- #### 1. Separation of Concerns (Complex Components)
107
-
108
- For complex components like WYSIWYG editors, separate concerns into handler classes:
109
-
110
- ```typescript
111
- export class DeesComplexComponent extends DeesElement {
112
- // Orchestrator pattern - main component coordinates handlers
113
- private inputHandler: InputHandler;
114
- private stateHandler: StateHandler;
115
- private renderHandler: RenderHandler;
116
-
117
- constructor() {
118
- super();
119
- this.inputHandler = new InputHandler(this);
120
- this.stateHandler = new StateHandler(this);
121
- this.renderHandler = new RenderHandler(this);
122
- }
123
- }
124
- ```
125
-
126
- #### 2. Singleton Pattern (Global Components)
127
-
128
- For global UI elements like menus:
129
-
130
- ```typescript
131
- export class DeesGlobalMenu extends DeesElement {
132
- private static instance: DeesGlobalMenu;
133
-
134
- public static getInstance(): DeesGlobalMenu {
135
- if (!DeesGlobalMenu.instance) {
136
- DeesGlobalMenu.instance = new DeesGlobalMenu();
137
- document.body.appendChild(DeesGlobalMenu.instance);
138
- }
139
- return DeesGlobalMenu.instance;
140
- }
141
- }
142
- ```
143
-
144
- #### 3. Registry Pattern (Z-Index Management)
145
-
146
- Use centralized registries for global state:
147
-
148
- ```typescript
149
- class ComponentRegistry {
150
- private static instance: ComponentRegistry;
151
- private registry = new WeakMap<HTMLElement, number>();
152
-
153
- public register(element: HTMLElement, value: number) {
154
- this.registry.set(element, value);
155
- }
156
-
157
- public unregister(element: HTMLElement) {
158
- this.registry.delete(element);
159
- }
160
- }
161
- ```
162
-
163
- ## Component Types and Base Classes
164
-
165
- ### Standard Component (extends DeesElement)
166
-
167
- Use for most UI components:
168
- - Buttons, badges, icons
169
- - Layout components
170
- - Data display components
171
- - Overlay components
172
-
173
- ### Form Input Component (extends DeesInputBase)
174
-
175
- Use for all form inputs:
176
- - Text inputs, dropdowns, checkboxes
177
- - Date pickers, file uploads
178
- - Rich text editors
179
-
180
- **Required implementations:**
181
- ```typescript
182
- export class DeesInputCustom extends DeesInputBase<ValueType> {
183
- // Required: Get current value
184
- public getValue(): ValueType {
185
- return this.value;
186
- }
187
-
188
- // Required: Set value programmatically
189
- public setValue(value: ValueType): void {
190
- this.value = value;
191
- this.changeSubject.next(this); // Notify form
192
- }
193
-
194
- // Optional: Custom validation
195
- public async validate(): Promise<boolean> {
196
- // Custom validation logic
197
- return true;
198
- }
199
- }
200
- ```
201
-
202
- ## Theming System
203
-
204
- ### DO: Use Design Tokens
205
-
206
- The canonical theming system is the `--dees-*` token vocabulary defined in `ts_web/elements/00theme.ts`. Import `themeDefaultStyles` as the **first** entry of every component's static styles and style exclusively through tokens:
207
-
208
- ```typescript
209
- import { themeDefaultStyles } from '../00theme.js';
210
-
211
- public static styles = [
212
- themeDefaultStyles,
213
- cssManager.defaultStyles,
214
- css`
215
- :host {
216
- background: var(--dees-color-bg-primary);
217
- color: var(--dees-color-text-primary);
218
- border: 1px solid var(--dees-color-border-default);
219
- border-radius: var(--dees-radius-md);
220
- padding: var(--dees-spacing-md) var(--dees-spacing-lg);
221
- transition: background var(--dees-transition-default);
222
- }
223
- :host(:hover) {
224
- background: var(--dees-color-hover);
225
- }
226
- `
227
- ];
228
-
229
- // ❌ INCORRECT
230
- background: #ffffff; // hard-coded color
231
- background: ${cssManager.bdTheme('#fff', '#09090b')}; // legacy per-call theming
232
- ```
233
-
234
- Token families: `--dees-color-bg-*`, `--dees-color-text-*` (incl. `-success`/`-warning`/`-error`), `--dees-color-border-*`, `--dees-color-accent-*`, `--dees-color-badge-*`, interactive states (`--dees-color-hover/active/pressed/row-hover`), `--dees-color-focus-ring`, tooltip/link/code/selection/scrollbar colors, plus `--dees-spacing-*`, `--dees-radius-*`, `--dees-shadow-*`, `--dees-transition-*`, and `--dees-control-height-*`. Check `00theme.ts` for the full list before inventing a value.
235
-
236
- ### DO: Derive Tints with color-mix
237
-
238
- For alpha tints of a semantic color (hover washes, selection tints), derive from the token instead of hard-coding another color pair:
239
-
240
- ```typescript
241
- background: color-mix(in srgb, var(--dees-color-accent-primary) 10%, transparent);
242
- ```
243
-
244
- ### Legacy: cssManager.bdTheme()
245
-
246
- `cssManager.bdTheme(lightValue, darkValue)` is the legacy per-call theming mechanism. It remains functional (the token layer in `00theme.ts` is itself implemented through it — that file is the single sanctioned usage), but **no new `bdTheme()` call sites are allowed** outside `00theme.ts`. A ratchet check (`node scripts/check-bdtheme-ratchet.cjs`, part of `pnpm test`) enforces this: per-file counts may only shrink. See `readme.theme-migration.md` for the bdTheme → token mapping used during migration.
247
-
248
- ## Event Handling
249
-
250
- ### DO: Dispatch Custom Events Properly
251
-
252
- ```typescript
253
- // ✅ CORRECT - Events bubble and cross shadow DOM
254
- this.dispatchEvent(new CustomEvent('dees-componentname-change', {
255
- detail: { value: this.value },
256
- bubbles: true,
257
- composed: true
258
- }));
259
-
260
- // ❌ INCORRECT - Event won't propagate properly
261
- this.dispatchEvent(new CustomEvent('change', {
262
- detail: { value: this.value }
263
- // Missing bubbles and composed
264
- }));
265
- ```
266
-
267
- ### DO: Use Event Delegation
268
-
269
- For dynamic content, use event delegation:
270
-
271
- ```typescript
272
- // ✅ CORRECT - Single listener for all items
273
- this.addEventListener('click', (e: MouseEvent) => {
274
- const item = (e.target as HTMLElement).closest('.item');
275
- if (item) {
276
- this.handleItemClick(item);
277
- }
278
- });
279
-
280
- // ❌ INCORRECT - Multiple listeners
281
- this.items.forEach(item => {
282
- item.addEventListener('click', () => this.handleItemClick(item));
283
- });
284
- ```
285
-
286
- ## State Management
287
-
288
- ### DO: Use Appropriate Property Decorators
289
-
290
- ```typescript
291
- // Public API - use @property
292
- @property({ type: String })
293
- public label: string;
294
-
295
- // Internal state - use @state
296
- @state()
297
- private isLoading: boolean = false;
298
-
299
- // Reflect to attribute when needed
300
- @property({ type: Boolean, reflect: true })
301
- public disabled: boolean = false;
302
- ```
303
-
304
- ### DON'T: Manipulate State in Render
305
-
306
- ```typescript
307
- // ❌ INCORRECT - Side effects in render
308
- public render() {
309
- this.counter++; // Don't modify state
310
- return html`<div>${this.counter}</div>`;
311
- }
312
-
313
- // ✅ CORRECT - Pure render function
314
- public render() {
315
- return html`<div>${this.counter}</div>`;
316
- }
317
- ```
318
-
319
- ## Form Components
320
-
321
- ### DO: Extend DeesInputBase
322
-
323
- All form inputs must extend the base class:
324
-
325
- ```typescript
326
- export class DeesInputNew extends DeesInputBase<string> {
327
- // Inherits: key, label, value, required, disabled, validationState
328
- }
329
- ```
330
-
331
- ### DO: Emit Changes Consistently
332
-
333
- ```typescript
334
- private handleInput(e: Event) {
335
- this.value = (e.target as HTMLInputElement).value;
336
- this.changeSubject.next(this); // Notify form system
337
- }
338
- ```
339
-
340
- ### DO: Support Standard Form Properties
341
-
342
- ```typescript
343
- // All form inputs should support:
344
- @property() public key: string;
345
- @property() public label: string;
346
- @property() public required: boolean = false;
347
- @property() public disabled: boolean = false;
348
- @property() public validationState: 'valid' | 'warn' | 'invalid';
349
- ```
350
-
351
- ## Overlay Components
352
-
353
- ### DO: Use Z-Index Registry
354
-
355
- Never hardcode z-index values:
356
-
357
- ```typescript
358
- // ✅ CORRECT
359
- import { zIndexRegistry } from './00zindex.js';
360
-
361
- public async show() {
362
- this.modalZIndex = zIndexRegistry.getNextZIndex();
363
- zIndexRegistry.register(this, this.modalZIndex);
364
- this.style.zIndex = `${this.modalZIndex}`;
365
- }
366
-
367
- public async hide() {
368
- zIndexRegistry.unregister(this);
369
- }
370
-
371
- // ❌ INCORRECT
372
- public async show() {
373
- this.style.zIndex = '9999'; // Hardcoded z-index
374
- }
375
- ```
376
-
377
- ### DO: Use Window Layers
378
-
379
- For modal backdrops:
380
-
381
- ```typescript
382
- import { DeesWindowLayer } from './dees-windowlayer.js';
383
-
384
- private windowLayer: DeesWindowLayer;
385
-
386
- public async show() {
387
- this.windowLayer = new DeesWindowLayer();
388
- this.windowLayer.zIndex = zIndexRegistry.getNextZIndex();
389
- document.body.append(this.windowLayer);
390
- }
391
- ```
392
-
393
- ## Complex Components
394
-
395
- ### DO: Use Handler Classes
396
-
397
- For complex logic, separate into specialized handlers:
398
-
399
- ```typescript
400
- // wysiwyg/handlers/input.handler.ts
401
- export class InputHandler {
402
- constructor(private component: DeesInputWysiwyg) {}
403
-
404
- public handleInput(event: InputEvent) {
405
- // Specialized input handling
406
- }
407
- }
408
-
409
- // Main component orchestrates
410
- export class DeesInputWysiwyg extends DeesInputBase {
411
- private inputHandler = new InputHandler(this);
412
- }
413
- ```
414
-
415
- ### DO: Use Programmatic Rendering
416
-
417
- For performance-critical updates that shouldn't trigger re-renders:
418
-
419
- ```typescript
420
- // ✅ CORRECT - Direct DOM manipulation when needed
421
- private updateBlockContent(blockId: string, content: string) {
422
- const blockElement = this.shadowRoot.querySelector(`#${blockId}`);
423
- if (blockElement) {
424
- blockElement.textContent = content; // Direct update
425
- }
426
- }
427
-
428
- // ❌ INCORRECT - Triggering full re-render
429
- private updateBlockContent(blockId: string, content: string) {
430
- this.blocks.find(b => b.id === blockId).content = content;
431
- this.requestUpdate(); // Unnecessary re-render
432
- }
433
- ```
434
-
435
- ## Streaming Components (harness pattern)
436
-
437
- Components that render continuously arriving data (agent chat, logs) follow the harness pattern from `00group-harness`:
438
-
439
- - **Props are the source of truth; deltas are the fast path.** The host owns the array (e.g. `.messages`). Incremental updates go through `applyDelta()`, which mutates the same object instance held in the array and normally calls `requestUpdate()` on the one affected child element. Top-level tool updates also refresh derived activity. A later wholesale re-assignment of the array is therefore idempotent — no divergence between streamed and re-set state.
440
- - **Keyed `repeat` for identity.** List children render via `directives.repeat` keyed by a stable id so streaming never recreates sibling DOM (which would drop scroll position, expansion state, and selection).
441
- - **Cheap while streaming, rich when done.** `markdownWhileStreaming` defaults to `true`, so active markdown is parsed on a throttled ≥150 ms, token-guarded path while syntax highlighting waits for the final parse. Set the policy to `false` on a chat, message list, message, or tool card to keep active text plain and perform one final parse at `message-end`; the assembled chat forwards the setting through outer messages and nested subtask transcripts.
442
- - **Scroll follow with user override.** Auto-scroll only when the user is already pinned to the bottom (`scrollHeight - scrollTop - clientHeight < threshold`); otherwise show a "jump to latest" chip. Intent-based following remains active until explicit user scroll intent, while deferred frame checks and the `ResizeObserver` re-pin only when the scroll content grows. Streamed growth and the scroll therefore move as one motion without treating disclosure shrink as new content. Disconnect the observer and clear its size baseline in `disconnectedCallback`; retained hidden nested transcripts disable auto-follow observation until reopened.
443
- - **Interaction state lives on the child.** Expand/collapse (reasoning, tool cards) is element-local state seeded from streaming state, descriptor defaults, errors, and authoritative subtask status; once the user toggles it, parent re-renders cannot reset that choice.
444
- - **Nested streams replace authoritatively, then delta in place.** A subtask preview arrives through the parent tool call as a complete bounded snapshot. Existing child messages may then receive one-level deltas addressed by `parentMessageId`; a delta never invents a missing child message. The producer adds that message to its source-of-truth snapshot first.
445
- - **Visual grouping never changes ownership.** Consecutive tool calls use one generic chronological grid pipeline partitioned by registry layout metadata: compact tools share compact rows, subagents share only subtask rows, and full-row tools span every column. Each segment's nested repeat remains keyed by message id. Status changes never move a card to another owner; untouched projected subtasks collapse individually while the grid remains in place. Group ids remain unique through segment splits/merges and survive front-window eviction as long as one card remains.
446
- - **Disclosure animation is lazy and retained.** A collapsed tool body is not mounted until first use. Its first open starts from a painted closed state; later opens and closes animate the same body node, which is inert and aria-hidden while collapsed.
447
- - **Inline recursion is explicit and bounded.** The outer list may render subtask previews; nested lists set `allowSubtaskStreams` false. Full descendant sessions remain reachable through the drill-in event rather than recursively expanding the transcript.
448
-
449
- ## Performance Optimization
450
-
451
- ### DO: Debounce Expensive Operations
452
-
453
- ```typescript
454
- private resizeTimeout: number;
455
-
456
- private handleResize = () => {
457
- clearTimeout(this.resizeTimeout);
458
- this.resizeTimeout = window.setTimeout(() => {
459
- this.updateLayout();
460
- }, 250);
461
- };
462
- ```
463
-
464
- ### DO: Use Observers Efficiently
465
-
466
- ```typescript
467
- // Clean up observers
468
- public disconnectedCallback() {
469
- super.disconnectedCallback();
470
- this.resizeObserver?.disconnect();
471
- this.mutationObserver?.disconnect();
472
- }
473
- ```
474
-
475
- ### DO: Implement Virtual Scrolling
476
-
477
- For large lists:
478
-
479
- ```typescript
480
- // Only render visible items
481
- private getVisibleItems() {
482
- const scrollTop = this.scrollContainer.scrollTop;
483
- const containerHeight = this.scrollContainer.clientHeight;
484
- const itemHeight = 50;
485
-
486
- const startIndex = Math.floor(scrollTop / itemHeight);
487
- const endIndex = Math.ceil((scrollTop + containerHeight) / itemHeight);
488
-
489
- return this.items.slice(startIndex, endIndex);
490
- }
491
- ```
492
-
493
- ## Focus Management
494
-
495
- ### DO: Handle Focus Timing
496
-
497
- ```typescript
498
- // ✅ CORRECT - Wait for render
499
- async focusInput() {
500
- await this.updateComplete;
501
- await new Promise(resolve => requestAnimationFrame(resolve));
502
- this.inputElement?.focus();
503
- }
504
-
505
- // ❌ INCORRECT - Focus too early
506
- focusInput() {
507
- this.inputElement?.focus(); // Element might not exist
508
- }
509
- ```
510
-
511
- ### DO: Prevent Focus Loss
512
-
513
- ```typescript
514
- // For global menus
515
- constructor() {
516
- super();
517
- // Prevent focus loss when clicking menu
518
- this.addEventListener('mousedown', (e) => {
519
- e.preventDefault();
520
- });
521
- }
522
- ```
523
-
524
- ### DO: Implement Blur Debouncing
525
-
526
- ```typescript
527
- private blurTimeout: number;
528
-
529
- private handleBlur = () => {
530
- clearTimeout(this.blurTimeout);
531
- this.blurTimeout = window.setTimeout(() => {
532
- // Check if truly blurred
533
- if (!this.contains(document.activeElement)) {
534
- this.handleTrueBlur();
535
- }
536
- }, 100);
537
- };
538
- ```
539
-
540
- ## Demo System
541
-
542
- ### DO: Create Comprehensive Demos
543
-
544
- Every component needs a demo:
545
-
546
- ```typescript
547
- // dees-button.demo.ts
548
- import { html } from '@design.estate/dees-element';
549
-
550
- export const demoFunc = () => html`
551
- <dees-button>Default Button</dees-button>
552
- <dees-button type="primary">Primary Button</dees-button>
553
- <dees-button type="danger" disabled>Disabled Danger</dees-button>
554
- `;
555
-
556
- // In component file
557
- import * as demoFunc from './dees-button.demo.js';
558
-
559
- export class DeesButton extends DeesElement {
560
- public static demo = demoFunc.demoFunc;
561
- }
562
- ```
563
-
564
- ### DO: Include All Variants
565
-
566
- Show all component states and variations in demos:
567
- - Default state
568
- - Different types/variants
569
- - Disabled state
570
- - Loading state
571
- - Error states
572
- - Edge cases (long text, empty content)
573
-
574
- ## Common Pitfalls and Anti-patterns
575
-
576
- ### ❌ DON'T: Hardcode Z-Index Values
577
-
578
- ```typescript
579
- // ❌ WRONG
580
- this.style.zIndex = '9999';
581
-
582
- // ✅ CORRECT
583
- this.style.zIndex = `${zIndexRegistry.getNextZIndex()}`;
584
- ```
585
-
586
- ### ❌ DON'T: Skip Base Classes
587
-
588
- ```typescript
589
- // ❌ WRONG - Form input without base class
590
- export class DeesInputCustom extends DeesElement {
591
- // Missing standard form functionality
592
- }
593
-
594
- // ✅ CORRECT
595
- export class DeesInputCustom extends DeesInputBase<string> {
596
- // Inherits all form functionality
597
- }
598
- ```
599
-
600
- ### ❌ DON'T: Forget Theme Support
601
-
602
- ```typescript
603
- // ❌ WRONG
604
- background-color: #ffffff;
605
- color: #000000;
606
-
607
- // ✅ CORRECT
608
- background-color: var(--dees-color-bg-primary);
609
- color: var(--dees-color-text-primary);
610
- ```
611
-
612
- ### ❌ DON'T: Create Components Without Demos
613
-
614
- ```typescript
615
- // ❌ WRONG
616
- export class DeesComponent extends DeesElement {
617
- // No demo property
618
- }
619
-
620
- // ✅ CORRECT
621
- export class DeesComponent extends DeesElement {
622
- public static demo = demoFunc.demoFunc;
623
- }
624
- ```
625
-
626
- ### ❌ DON'T: Emit Non-Bubbling Events
627
-
628
- ```typescript
629
- // ❌ WRONG
630
- this.dispatchEvent(new CustomEvent('change', {
631
- detail: this.value
632
- }));
633
-
634
- // ✅ CORRECT
635
- this.dispatchEvent(new CustomEvent('change', {
636
- detail: this.value,
637
- bubbles: true,
638
- composed: true
639
- }));
640
- ```
641
-
642
- ### ❌ DON'T: Skip Cleanup
643
-
644
- ```typescript
645
- // ❌ WRONG
646
- public connectedCallback() {
647
- window.addEventListener('resize', this.handleResize);
648
- }
649
-
650
- // ✅ CORRECT
651
- public connectedCallback() {
652
- super.connectedCallback();
653
- window.addEventListener('resize', this.handleResize);
654
- }
655
-
656
- public disconnectedCallback() {
657
- super.disconnectedCallback();
658
- window.removeEventListener('resize', this.handleResize);
659
- }
660
- ```
661
-
662
- ### ❌ DON'T: Use Inline Styles for Theming
663
-
664
- ```typescript
665
- // ❌ WRONG
666
- <div style="background-color: ${this.darkMode ? '#000' : '#fff'}">
667
-
668
- // ✅ CORRECT
669
- <div class="themed-container">
670
- // In styles:
671
- .themed-container {
672
- background-color: var(--dees-color-bg-primary);
673
- }
674
- ```
675
-
676
- ### ❌ DON'T: Forget Mobile Responsiveness
677
-
678
- ```typescript
679
- // ❌ WRONG
680
- :host {
681
- width: 800px; // Fixed width
682
- }
683
-
684
- // ✅ CORRECT
685
- :host {
686
- width: 100%;
687
- max-width: 800px;
688
- }
689
-
690
- @media (max-width: 768px) {
691
- :host {
692
- /* Mobile adjustments */
693
- }
694
- }
695
- ```
696
-
697
- ## Code Examples
698
-
699
- ### Example: Creating a New Button Variant
700
-
701
- ```typescript
702
- // dees-special-button.ts
703
- import { customElement, property, css, html, cssManager } from '@design.estate/dees-element';
704
- import { DeesElement } from '@design.estate/dees-element';
705
- import { themeDefaultStyles } from '../00theme.js';
706
- import * as demoFunc from './dees-special-button.demo.js';
707
-
708
- @customElement('dees-special-button')
709
- export class DeesSpecialButton extends DeesElement {
710
- public static demo = demoFunc.demoFunc;
711
-
712
- @property({ type: String })
713
- public text: string = 'Click me';
714
-
715
- @property({ type: Boolean, reflect: true })
716
- public loading: boolean = false;
717
-
718
- public static styles = [
719
- themeDefaultStyles,
720
- cssManager.defaultStyles,
721
- css`
722
- :host {
723
- display: inline-block;
724
- }
725
-
726
- .button {
727
- padding: var(--dees-spacing-sm) var(--dees-spacing-lg);
728
- background: var(--dees-color-accent-primary);
729
- color: white;
730
- border: none;
731
- border-radius: var(--dees-radius-sm);
732
- cursor: pointer;
733
- transition: all var(--dees-transition-slow);
734
- }
735
-
736
- .button:hover {
737
- transform: translateY(-2px);
738
- box-shadow: var(--dees-shadow-md);
739
- }
740
-
741
- :host([loading]) .button {
742
- opacity: 0.7;
743
- cursor: not-allowed;
744
- }
745
- `
746
- ];
747
-
748
- public render() {
749
- return html`
750
- <button class="button" ?disabled=${this.loading} @click=${this.handleClick}>
751
- ${this.loading ? html`<dees-spinner size="small"></dees-spinner>` : this.text}
752
- </button>
753
- `;
754
- }
755
-
756
- private handleClick() {
757
- this.dispatchEvent(new CustomEvent('special-click', {
758
- bubbles: true,
759
- composed: true
760
- }));
761
- }
762
- }
763
- ```
764
-
765
- ### Example: Creating a Form Input
766
-
767
- ```typescript
768
- // dees-input-special.ts
769
- export class DeesInputSpecial extends DeesInputBase<string> {
770
- public static demo = demoFunc.demoFunc;
771
-
772
- public render() {
773
- return html`
774
- <dees-label .label=${this.label} .required=${this.required}>
775
- <input
776
- type="text"
777
- .value=${this.value || ''}
778
- ?disabled=${this.disabled}
779
- @input=${this.handleInput}
780
- @blur=${this.handleBlur}
781
- />
782
- </dees-label>
783
- `;
784
- }
785
-
786
- private handleInput(e: Event) {
787
- this.value = (e.target as HTMLInputElement).value;
788
- this.changeSubject.next(this);
789
- }
790
-
791
- private handleBlur() {
792
- this.dispatchEvent(new CustomEvent('blur', {
793
- bubbles: true,
794
- composed: true
795
- }));
796
- }
797
-
798
- public getValue(): string {
799
- return this.value;
800
- }
801
-
802
- public setValue(value: string): void {
803
- this.value = value;
804
- this.changeSubject.next(this);
805
- }
806
- }
807
- ```
808
-
809
- ## Summary
810
-
811
- This playbook represents the collective wisdom and patterns found in the @design.estate/dees-catalog component library. Following these guidelines will help you create components that are:
812
-
813
- - **Consistent**: Following established patterns
814
- - **Maintainable**: Easy to understand and modify
815
- - **Performant**: Optimized for real-world use
816
- - **Accessible**: Usable by everyone
817
- - **Theme-aware**: Supporting light and dark modes
818
- - **Well-integrated**: Working seamlessly with the component ecosystem
819
-
820
- Remember: When in doubt, look at existing components for examples. The codebase itself is the best documentation of these patterns in action.