@turquoisehealth/pit-viper 2.182.1-dev.5 → 2.183.1-dev.0
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/claude-plugin/skills/pit-viper/SKILL.md +28 -31
- package/package.json +1 -1
- package/pv-components/dist/stats/vue/visualizations/stats.html +1 -1
- package/pv-components/dist/vue/visualizations/components/tables/PvDataTable/table-components/FilterGroupMenu.vue.d.ts +21 -0
- package/pv-components/dist/vue/visualizations/components/tables/PvDataTable/types.d.ts +9 -0
- package/pv-components/dist/vue/visualizations/components/tables/PvDataTable/useSetFilter.d.ts +1 -0
- package/pv-components/dist/vue/visualizations/pv-components-visualizations.mjs +1610 -1426
- package/pv-components/dist/vue/visualizations/pv-components-visualizations.mjs.map +1 -1
- package/claude-plugin/skills/pit-viper/references/design-language.md +0 -80
- package/claude-plugin/skills/pit-viper/references/design-rules.md +0 -265
- package/claude-plugin/skills/pit-viper/references/html-patterns.md +0 -468
- package/claude-plugin/skills/pit-viper/references/layout-patterns.md +0 -367
- package/claude-plugin/skills/pit-viper/references/patterns-core.md +0 -97
- package/claude-plugin/skills/pit-viper/references/theme-guide.md +0 -160
- package/claude-plugin/skills/pit-viper/references/vue-guidelines.md +0 -526
|
@@ -1,468 +0,0 @@
|
|
|
1
|
-
# HTML / Web Component Patterns
|
|
2
|
-
|
|
3
|
-
Use this reference when generating HTML with Pit Viper CSS classes and web components (HTML Mode). For smol MCP tools, always use this mode.
|
|
4
|
-
|
|
5
|
-
## Contents
|
|
6
|
-
|
|
7
|
-
- Adding Pit Viper (CSS theme + web component bundle)
|
|
8
|
-
- Naming Conventions (kebab-case tags and attributes)
|
|
9
|
-
- Web Component Best Practices (closing tags, slot wrappers, complex props via JS)
|
|
10
|
-
- Component Examples (button, input, card)
|
|
11
|
-
- Event Handling
|
|
12
|
-
- CSS Classes (pv-* utilities)
|
|
13
|
-
- Tables (pv-table variants)
|
|
14
|
-
- Limitations (vs Vue)
|
|
15
|
-
- Complete Example
|
|
16
|
-
- Anti-Patterns / Good Patterns
|
|
17
|
-
|
|
18
|
-
## Adding Pit Viper to Your Project
|
|
19
|
-
|
|
20
|
-
### Step 1: Include CSS
|
|
21
|
-
|
|
22
|
-
Choose one CSS theme based on your use case:
|
|
23
|
-
|
|
24
|
-
| Theme | File | Use Case |
|
|
25
|
-
|-------|------|----------|
|
|
26
|
-
| Platform | `pit-viper-v2.css` | Internal tools, dashboards |
|
|
27
|
-
| Consumer | `pit-viper-consumer.css` | Marketing, public-facing sites |
|
|
28
|
-
|
|
29
|
-
### Step 2: Include Web Components
|
|
30
|
-
|
|
31
|
-
```html
|
|
32
|
-
<script src="https://unpkg.com/@turquoisehealth/pit-viper@latest/pv-components/dist/web/pv-components.iife.js"></script>
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
This includes all base components with the Vue runtime bundled.
|
|
36
|
-
|
|
37
|
-
### Complete Setup Example
|
|
38
|
-
|
|
39
|
-
```html
|
|
40
|
-
<!DOCTYPE html>
|
|
41
|
-
<html lang="en">
|
|
42
|
-
<head>
|
|
43
|
-
<meta charset="UTF-8">
|
|
44
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
45
|
-
<title>Page Title</title>
|
|
46
|
-
|
|
47
|
-
<!-- v2 Platform theme (internal tools, dashboards) -->
|
|
48
|
-
<link rel="stylesheet" href="https://unpkg.com/@turquoisehealth/pit-viper@latest/_site/assets/css/pit-viper-v2.css">
|
|
49
|
-
|
|
50
|
-
<!-- v2 Marketing/Consumer theme (marketing sites, public-facing pages)
|
|
51
|
-
<link rel="stylesheet" href="https://unpkg.com/@turquoisehealth/pit-viper@latest/_site/assets/css/pit-viper-consumer.css">
|
|
52
|
-
-->
|
|
53
|
-
|
|
54
|
-
<!-- SVG sprite path for icons -->
|
|
55
|
-
<script>
|
|
56
|
-
window.__PV_GLOBAL_SPRITE_PATH__ = 'https://unpkg.com/@turquoisehealth/pit-viper@latest/_site/assets/sprite-v2.svg';
|
|
57
|
-
</script>
|
|
58
|
-
|
|
59
|
-
<!-- Web components for interactive elements -->
|
|
60
|
-
<script src="https://unpkg.com/@turquoisehealth/pit-viper@latest/pv-components/dist/web/pv-components.iife.js"></script>
|
|
61
|
-
</head>
|
|
62
|
-
<body>
|
|
63
|
-
<!-- Your content here -->
|
|
64
|
-
</body>
|
|
65
|
-
</html>
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Naming Conventions
|
|
69
|
-
|
|
70
|
-
Web components use kebab-case (unlike Vue which uses PascalCase):
|
|
71
|
-
|
|
72
|
-
| Vue Component | Web Component |
|
|
73
|
-
|---------------|---------------|
|
|
74
|
-
| `<PvButton>` | `<pv-button>` |
|
|
75
|
-
| `<PvInput>` | `<pv-input>` |
|
|
76
|
-
| `<PvSelectButton>` | `<pv-select-button>` |
|
|
77
|
-
| `<PvCard>` | `<pv-card>` |
|
|
78
|
-
|
|
79
|
-
Properties are also kebab-case:
|
|
80
|
-
|
|
81
|
-
| Vue Prop | HTML Attribute |
|
|
82
|
-
|----------|----------------|
|
|
83
|
-
| `leftIcon` | `left-icon` |
|
|
84
|
-
| `isLoading` | `is-loading` |
|
|
85
|
-
| `optionsVariant` | `options-variant` |
|
|
86
|
-
|
|
87
|
-
## Web Component Best Practices
|
|
88
|
-
|
|
89
|
-
These rules are critical for web components to work correctly:
|
|
90
|
-
|
|
91
|
-
### 1. Always Use Complete End Tags
|
|
92
|
-
|
|
93
|
-
Web components **require** full closing tags. Self-closing tags will break slots.
|
|
94
|
-
|
|
95
|
-
```html
|
|
96
|
-
<!-- CORRECT -->
|
|
97
|
-
<pv-button>Click me</pv-button>
|
|
98
|
-
<pv-card></pv-card>
|
|
99
|
-
|
|
100
|
-
<!-- WRONG - slots will not work -->
|
|
101
|
-
<pv-button />
|
|
102
|
-
<pv-card />
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### 2. Slots Must Use Div Wrappers
|
|
106
|
-
|
|
107
|
-
Unlike Vue's `<template #slot-name>`, web component slots need a wrapper element with the `slot` attribute:
|
|
108
|
-
|
|
109
|
-
```html
|
|
110
|
-
<!-- CORRECT for web components -->
|
|
111
|
-
<pv-card>
|
|
112
|
-
<div slot="header">Header content</div>
|
|
113
|
-
<div slot="default">Body content</div>
|
|
114
|
-
<div slot="footer">Footer content</div>
|
|
115
|
-
</pv-card>
|
|
116
|
-
|
|
117
|
-
<!-- Vue shorthand (does NOT work in web components) -->
|
|
118
|
-
<PvCard>
|
|
119
|
-
<template #header>Header content</template>
|
|
120
|
-
</PvCard>
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### 3. No Scoped or Conditional Slots
|
|
124
|
-
|
|
125
|
-
Web components do not support:
|
|
126
|
-
- Scoped slots (slots that pass data back to the parent)
|
|
127
|
-
- Conditional slots (`v-if="$slots.someSlot"` patterns)
|
|
128
|
-
|
|
129
|
-
Always render slot content statically.
|
|
130
|
-
|
|
131
|
-
### 4. Complex Props Require JavaScript
|
|
132
|
-
|
|
133
|
-
Arrays, objects, and functions cannot be passed as HTML attributes:
|
|
134
|
-
|
|
135
|
-
```html
|
|
136
|
-
<!-- Primitive props work as attributes -->
|
|
137
|
-
<pv-button variant="secondary" disabled>OK</pv-button>
|
|
138
|
-
|
|
139
|
-
<!-- Complex props must be set via JavaScript -->
|
|
140
|
-
<pv-select-button id="my-select"></pv-select-button>
|
|
141
|
-
<script>
|
|
142
|
-
document.getElementById('my-select').options = [
|
|
143
|
-
{ label: 'Option 1', value: '1' },
|
|
144
|
-
{ label: 'Option 2', value: '2' },
|
|
145
|
-
];
|
|
146
|
-
</script>
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### 5. Base Components Only
|
|
150
|
-
|
|
151
|
-
Web component support is limited to **base components** (buttons, inputs, cards, etc.).
|
|
152
|
-
|
|
153
|
-
**Not available as web components:**
|
|
154
|
-
- `PvDataTable` (use Vue or CSS tables)
|
|
155
|
-
- `PvChart` (requires Vue)
|
|
156
|
-
- Other visualization components
|
|
157
|
-
|
|
158
|
-
For more details, see the [Vue Web Components documentation](https://vuejs.org/guide/extras/web-components).
|
|
159
|
-
|
|
160
|
-
## Component Examples
|
|
161
|
-
|
|
162
|
-
### Button
|
|
163
|
-
|
|
164
|
-
The pv-button web component uses the `label` attribute for button text (not slot content).
|
|
165
|
-
|
|
166
|
-
```html
|
|
167
|
-
<pv-button label="Click me"></pv-button>
|
|
168
|
-
<pv-button label="Secondary" variant="secondary"></pv-button>
|
|
169
|
-
<pv-button label="Add Item" variant="ghost" left-icon="plus"></pv-button>
|
|
170
|
-
<pv-button label="Saving..." loading></pv-button>
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Input
|
|
174
|
-
```html
|
|
175
|
-
<pv-input placeholder="Enter text..." id="my-input"></pv-input>
|
|
176
|
-
|
|
177
|
-
<script>
|
|
178
|
-
const input = document.getElementById('my-input');
|
|
179
|
-
input.addEventListener('input', (e) => {
|
|
180
|
-
console.log('Value:', e.target.value);
|
|
181
|
-
});
|
|
182
|
-
</script>
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Card
|
|
186
|
-
```html
|
|
187
|
-
<pv-card>
|
|
188
|
-
<div slot="header">
|
|
189
|
-
<h2 class="pv-heading-2">Card Title</h2>
|
|
190
|
-
</div>
|
|
191
|
-
<div slot="default">
|
|
192
|
-
<p class="pv-text-body-md">Card content goes here.</p>
|
|
193
|
-
</div>
|
|
194
|
-
<div slot="footer">
|
|
195
|
-
<pv-button>Action</pv-button>
|
|
196
|
-
</div>
|
|
197
|
-
</pv-card>
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
## Event Handling
|
|
201
|
-
|
|
202
|
-
Web components emit custom events. Use `addEventListener`:
|
|
203
|
-
|
|
204
|
-
```html
|
|
205
|
-
<pv-button id="my-button">Click me</pv-button>
|
|
206
|
-
|
|
207
|
-
<script>
|
|
208
|
-
document.getElementById('my-button').addEventListener('click', () => {
|
|
209
|
-
console.log('Button clicked');
|
|
210
|
-
});
|
|
211
|
-
</script>
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
For components with custom events:
|
|
215
|
-
|
|
216
|
-
```html
|
|
217
|
-
<pv-select-button id="my-select"></pv-select-button>
|
|
218
|
-
|
|
219
|
-
<script>
|
|
220
|
-
const select = document.getElementById('my-select');
|
|
221
|
-
|
|
222
|
-
// Set options via JavaScript (complex props can't be set as attributes)
|
|
223
|
-
select.options = [
|
|
224
|
-
{ label: 'Option 1', value: '1' },
|
|
225
|
-
{ label: 'Option 2', value: '2' },
|
|
226
|
-
];
|
|
227
|
-
|
|
228
|
-
// Listen for value changes
|
|
229
|
-
select.addEventListener('update:modelValue', (e) => {
|
|
230
|
-
console.log('Selected:', e.detail);
|
|
231
|
-
});
|
|
232
|
-
</script>
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
## Complex Props
|
|
236
|
-
|
|
237
|
-
Non-primitive props (arrays, objects) must be set via JavaScript, not HTML attributes:
|
|
238
|
-
|
|
239
|
-
```html
|
|
240
|
-
<pv-data-table id="my-table"></pv-data-table>
|
|
241
|
-
|
|
242
|
-
<script>
|
|
243
|
-
const table = document.getElementById('my-table');
|
|
244
|
-
|
|
245
|
-
table.colDefs = [
|
|
246
|
-
{ field: 'name', headerName: 'Name' },
|
|
247
|
-
{ field: 'email', headerName: 'Email' },
|
|
248
|
-
];
|
|
249
|
-
|
|
250
|
-
table.rowData = [
|
|
251
|
-
{ name: 'Alice', email: 'alice@example.com' },
|
|
252
|
-
{ name: 'Bob', email: 'bob@example.com' },
|
|
253
|
-
];
|
|
254
|
-
</script>
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
## CSS Classes
|
|
258
|
-
|
|
259
|
-
Use pv-* utility classes for layout and styling:
|
|
260
|
-
|
|
261
|
-
```html
|
|
262
|
-
<div class="pv-flex pv-flex-vertical pv-stack-16">
|
|
263
|
-
<h1 class="pv-heading-1">Page Title</h1>
|
|
264
|
-
<p class="pv-text-body-md pv-text-subdued">Description text</p>
|
|
265
|
-
</div>
|
|
266
|
-
|
|
267
|
-
<div class="pv-card pv-inset-square-24 pv-radius">
|
|
268
|
-
<p class="pv-text-body-md">Card content</p>
|
|
269
|
-
</div>
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
## Tables
|
|
273
|
-
|
|
274
|
-
Use pv-table CSS classes for static tables. For interactive tables with sorting/filtering, use Vue with PvDataTable instead.
|
|
275
|
-
|
|
276
|
-
### Basic Table
|
|
277
|
-
|
|
278
|
-
```html
|
|
279
|
-
<table class="pv-table">
|
|
280
|
-
<thead>
|
|
281
|
-
<tr>
|
|
282
|
-
<th>Name</th>
|
|
283
|
-
<th>Email</th>
|
|
284
|
-
<th>Role</th>
|
|
285
|
-
</tr>
|
|
286
|
-
</thead>
|
|
287
|
-
<tbody>
|
|
288
|
-
<tr>
|
|
289
|
-
<td>Alice Johnson</td>
|
|
290
|
-
<td>alice@example.com</td>
|
|
291
|
-
<td>Admin</td>
|
|
292
|
-
</tr>
|
|
293
|
-
<tr>
|
|
294
|
-
<td>Bob Smith</td>
|
|
295
|
-
<td>bob@example.com</td>
|
|
296
|
-
<td>Editor</td>
|
|
297
|
-
</tr>
|
|
298
|
-
</tbody>
|
|
299
|
-
</table>
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
### Table Variants
|
|
303
|
-
|
|
304
|
-
| Class | Use Case |
|
|
305
|
-
|-------|----------|
|
|
306
|
-
| `.pv-table` | Standard table with header styling |
|
|
307
|
-
| `.pv-table-compressed` | Compact rows for dense data |
|
|
308
|
-
| `.pv-table-bordered` | Full borders on all cells |
|
|
309
|
-
| `.pv-table-matrix` | Matrix/grid layout with sticky headers |
|
|
310
|
-
|
|
311
|
-
### Sortable Headers
|
|
312
|
-
|
|
313
|
-
Use `data-sort` attribute for sort indicators:
|
|
314
|
-
|
|
315
|
-
```html
|
|
316
|
-
<th data-sort>Name</th>
|
|
317
|
-
<th data-sort="ascending">Email</th>
|
|
318
|
-
<th data-sort="descending">Date</th>
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
## Charts
|
|
322
|
-
|
|
323
|
-
**Charts are NOT available in HTML/Prototype mode.** Charting requires Vue components (PvChart, PvDataTableWithChart).
|
|
324
|
-
|
|
325
|
-
If the user requests a chart in HTML mode:
|
|
326
|
-
1. Explain that charts require Vue components
|
|
327
|
-
2. Offer to switch to Vue Mode if appropriate
|
|
328
|
-
3. Or use a placeholder with a note: "Chart visualization requires Vue implementation"
|
|
329
|
-
|
|
330
|
-
## Limitations
|
|
331
|
-
|
|
332
|
-
Web components have some differences from Vue components:
|
|
333
|
-
|
|
334
|
-
| Limitation | Workaround |
|
|
335
|
-
|------------|------------|
|
|
336
|
-
| No v-model | Use events + property assignment |
|
|
337
|
-
| No scoped slots | Use named slots with `slot="name"` |
|
|
338
|
-
| No conditional slots | Always render slot content |
|
|
339
|
-
| Complex props as attributes | Set via JavaScript |
|
|
340
|
-
| No Vue reactivity | Manually update properties |
|
|
341
|
-
| No charts | Charts require Vue — use PvChart |
|
|
342
|
-
|
|
343
|
-
## Complete Example
|
|
344
|
-
|
|
345
|
-
```html
|
|
346
|
-
<!DOCTYPE html>
|
|
347
|
-
<html lang="en">
|
|
348
|
-
<head>
|
|
349
|
-
<meta charset="UTF-8">
|
|
350
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
351
|
-
<title>Login</title>
|
|
352
|
-
<link rel="stylesheet" href="https://unpkg.com/@turquoisehealth/pit-viper@latest/_site/assets/css/pit-viper-v2.css">
|
|
353
|
-
<script>
|
|
354
|
-
window.__PV_GLOBAL_SPRITE_PATH__ = 'https://unpkg.com/@turquoisehealth/pit-viper@latest/_site/assets/sprite-v2.svg';
|
|
355
|
-
</script>
|
|
356
|
-
<script src="https://unpkg.com/@turquoisehealth/pit-viper@latest/pv-components/dist/web/pv-components.iife.js"></script>
|
|
357
|
-
</head>
|
|
358
|
-
<body class="pv-surface-accent">
|
|
359
|
-
<div class="pv-flex" style="min-height: 100vh; align-items: center; justify-content: center;">
|
|
360
|
-
<pv-card style="width: 400px;">
|
|
361
|
-
<div slot="header">
|
|
362
|
-
<h1 class="pv-heading-2">Sign In</h1>
|
|
363
|
-
</div>
|
|
364
|
-
<div slot="default" class="pv-flow-16">
|
|
365
|
-
<div>
|
|
366
|
-
<label class="pv-text-title-sm pv-stack-4">Email</label>
|
|
367
|
-
<pv-input id="email" type="email" placeholder="you@example.com"></pv-input>
|
|
368
|
-
</div>
|
|
369
|
-
<div>
|
|
370
|
-
<label class="pv-text-title-sm pv-stack-4">Password</label>
|
|
371
|
-
<pv-input id="password" type="password" placeholder="Enter password"></pv-input>
|
|
372
|
-
</div>
|
|
373
|
-
</div>
|
|
374
|
-
<div slot="footer">
|
|
375
|
-
<pv-button id="submit" label="Sign In" style="width: 100%;"></pv-button>
|
|
376
|
-
</div>
|
|
377
|
-
</pv-card>
|
|
378
|
-
</div>
|
|
379
|
-
|
|
380
|
-
<script>
|
|
381
|
-
document.getElementById('submit').addEventListener('click', () => {
|
|
382
|
-
const email = document.getElementById('email').value;
|
|
383
|
-
const password = document.getElementById('password').value;
|
|
384
|
-
console.log('Login attempt:', { email, password });
|
|
385
|
-
});
|
|
386
|
-
</script>
|
|
387
|
-
</body>
|
|
388
|
-
</html>
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
---
|
|
392
|
-
|
|
393
|
-
## Anti-Patterns
|
|
394
|
-
|
|
395
|
-
### Button Slot Content Instead of Label
|
|
396
|
-
|
|
397
|
-
Web components require the `label` attribute. Slot content won't render.
|
|
398
|
-
|
|
399
|
-
```html
|
|
400
|
-
<!-- BAD: Slot content -->
|
|
401
|
-
<pv-button>Click me</pv-button>
|
|
402
|
-
<pv-button variant="secondary">Cancel</pv-button>
|
|
403
|
-
|
|
404
|
-
<!-- GOOD: Label attribute -->
|
|
405
|
-
<pv-button label="Click me"></pv-button>
|
|
406
|
-
<pv-button variant="secondary" label="Cancel"></pv-button>
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
---
|
|
410
|
-
|
|
411
|
-
## Good Patterns
|
|
412
|
-
|
|
413
|
-
### Sidebar Navigation
|
|
414
|
-
|
|
415
|
-
```html
|
|
416
|
-
<div class="pv-card pv-inset-square-8 pv-flex pv-stack-24 pv-full-width">
|
|
417
|
-
<div class="pv-flex">
|
|
418
|
-
<img src="logo.png" alt="Logo" class="pv-icon-24" />
|
|
419
|
-
<span class="pv-text-title-lg pv-text-default">Organization Name</span>
|
|
420
|
-
</div>
|
|
421
|
-
</div>
|
|
422
|
-
|
|
423
|
-
<ul role="list" class="pv-popover-list" style="padding: 0px; --flow-size: 2px;">
|
|
424
|
-
<li data-active>
|
|
425
|
-
<a href="/overview" style="text-decoration: none; cursor: pointer;">
|
|
426
|
-
<svg aria-hidden="true" class="pv-icon pv-text-subdued">
|
|
427
|
-
<use xlink:href="#file"></use>
|
|
428
|
-
</svg>
|
|
429
|
-
<span class="pv-text-body-md">Overview</span>
|
|
430
|
-
</a>
|
|
431
|
-
</li>
|
|
432
|
-
<li>
|
|
433
|
-
<a href="/settings" style="text-decoration: none; cursor: pointer;">
|
|
434
|
-
<svg aria-hidden="true" class="pv-icon pv-text-subdued">
|
|
435
|
-
<use xlink:href="#sliders-horizontal"></use>
|
|
436
|
-
</svg>
|
|
437
|
-
<span class="pv-text-body-md">Settings</span>
|
|
438
|
-
</a>
|
|
439
|
-
</li>
|
|
440
|
-
</ul>
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
**What makes this good:**
|
|
444
|
-
- Uses `pv-card` with `pv-inset-square-8` for consistent padding
|
|
445
|
-
- Uses `pv-popover-list` for nav styling (reused for sidebar nav — same visual pattern)
|
|
446
|
-
- Uses `data-active` attribute for active state
|
|
447
|
-
- Uses `pv-icon` and `pv-text-subdued` for icon styling
|
|
448
|
-
- Minimal inline styles for link resets only
|
|
449
|
-
|
|
450
|
-
### Form Fields with Labels
|
|
451
|
-
|
|
452
|
-
```html
|
|
453
|
-
<div class="pv-stack-24">
|
|
454
|
-
<div>
|
|
455
|
-
<label class="pv-text-title-sm pv-stack-4">Group Name</label>
|
|
456
|
-
<pv-input placeholder="Enter group name"></pv-input>
|
|
457
|
-
</div>
|
|
458
|
-
<div>
|
|
459
|
-
<label class="pv-text-title-sm pv-stack-4">Description</label>
|
|
460
|
-
<pv-input placeholder="Optional description"></pv-input>
|
|
461
|
-
</div>
|
|
462
|
-
</div>
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
**What makes this good:**
|
|
466
|
-
- Uses `pv-stack-4` between label and input (tight coupling)
|
|
467
|
-
- Uses `pv-stack-24` between field groups (section spacing)
|
|
468
|
-
- Uses `pv-text-title-sm` for labels (consistent typography)
|