@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.
@@ -1,526 +0,0 @@
1
- # Vue Guidelines for Pit Viper
2
-
3
- Use this reference when generating Vue SFC code (Vue Mode).
4
-
5
- ## Contents
6
-
7
- - Adding Pit Viper (install, CSS, imports)
8
- - File Structure (script/template/style order)
9
- - Import Paths (base, visualizations, layout)
10
- - Props, Emits, Generics Patterns
11
- - v-model Patterns (input, select, modal, tabs)
12
- - Naming Conventions
13
- - Common Mistakes
14
- - Styling (pv-* classes only)
15
- - Tables & Charts (PvDataTable, PvChart)
16
- - Anti-Patterns / Good Patterns
17
-
18
- ## Adding Pit Viper to a Vue Project
19
-
20
- ### Step 1: Install the package
21
-
22
- ```bash
23
- npm install @turquoisehealth/pit-viper
24
- # or
25
- pnpm add @turquoisehealth/pit-viper
26
- ```
27
-
28
- ### Step 2: Import CSS in your app entry point
29
-
30
- ```typescript
31
- // main.ts or App.vue
32
- import '@turquoisehealth/pit-viper/pit-viper-v2.css'
33
- // OR for consumer/marketing sites:
34
- // import '@turquoisehealth/pit-viper/pit-viper-consumer.css'
35
- ```
36
-
37
- ### Step 3: Import and use components
38
-
39
- ```typescript
40
- import { PvButton, PvInput } from '@turquoisehealth/pit-viper/components'
41
- ```
42
-
43
- For complete setup instructions and sandboxes, see the [Storybook Introduction](https://pit-viper-storybook.netlify.app/?path=/docs/introduction--documentation).
44
-
45
- ## File Structure
46
-
47
- Components use Vue 3 Composition API with TypeScript. Block order is strict:
48
-
49
- 1. `<script lang="ts">` — Only for generic type definitions (when needed)
50
- 2. `<script setup lang="ts">` — Component logic
51
- 3. `<template>` — Markup
52
- 4. `<style scoped>` — Only when absolutely necessary (prefer pv-* classes)
53
-
54
- ## Import Paths
55
-
56
- Never use barrel exports. Import directly from component paths:
57
-
58
- ```typescript
59
- // Base components
60
- import { PvButton, PvInput, PvModal } from '@turquoisehealth/pit-viper/components'
61
-
62
- // Visualization components (tables, charts)
63
- import { PvDataTable, PvChart } from '@turquoisehealth/pit-viper/components/visualizations'
64
-
65
- // Layout components (must import directly)
66
- import PvSidePanel from '@turquoisehealth/pit-viper/components/layout/PvSidePanel/PvSidePanel.vue'
67
- ```
68
-
69
- ## Props Pattern
70
-
71
- Define props as interfaces. Use `withDefaults` for default values. Props should be alphabetically ordered.
72
-
73
- ```typescript
74
- export interface MyComponentProps {
75
- disabled?: boolean;
76
- label: string;
77
- options: Option[];
78
- placeholder?: string;
79
- }
80
-
81
- const props = withDefaults(defineProps<MyComponentProps>(), {
82
- disabled: false,
83
- options: () => [],
84
- placeholder: 'Select...',
85
- });
86
- ```
87
-
88
- ## Emits Pattern
89
-
90
- Define emits with kebab-case event names and typed payloads:
91
-
92
- ```typescript
93
- const emit = defineEmits<{
94
- (e: 'value-change', value: string): void;
95
- (e: 'item-select', item: Option): void;
96
- }>();
97
- ```
98
-
99
- ## Generics Pattern
100
-
101
- When using generics, declare interfaces in a separate `<script>` block:
102
-
103
- ```vue
104
- <script lang="ts">
105
- export interface ListProps<T> {
106
- items: T[];
107
- selected: T | null;
108
- }
109
- </script>
110
-
111
- <script setup lang="ts" generic="T">
112
- const props = defineProps<ListProps<T>>();
113
- </script>
114
- ```
115
-
116
- ## v-model Patterns
117
-
118
- ### PvInput
119
- ```vue
120
- <PvInput v-model="searchQuery" placeholder="Search..." />
121
- ```
122
-
123
- ### PvSelectButton
124
- ```vue
125
- <PvSelectButton
126
- v-model="selected"
127
- :options="options"
128
- label="Choose option"
129
- placeholder="Select..."
130
- />
131
- ```
132
-
133
- ### PvModal
134
- ```vue
135
- <PvModal v-model="isOpen" header="Confirm Action">
136
- <template #body>
137
- <p>Are you sure?</p>
138
- </template>
139
- <template #footer>
140
- <PvButton variant="ghost" @click="isOpen = false">Cancel</PvButton>
141
- <PvButton @click="confirm">Confirm</PvButton>
142
- </template>
143
- </PvModal>
144
- ```
145
-
146
- ### PvTabs
147
- ```vue
148
- <PvTabs v-model="activeTab" :tabs="[
149
- { label: 'Overview', value: 'overview' },
150
- { label: 'Settings', value: 'settings' },
151
- ]" />
152
- ```
153
-
154
- ## Naming Conventions
155
-
156
- Use PascalCase for component names AND HTML attributes (not kebab-case):
157
-
158
- ```vue
159
- <!-- Correct -->
160
- <PvButton leftIcon="plus" isLoading />
161
- <PvSelectButton optionsVariant="checkbox" />
162
-
163
- <!-- Incorrect -->
164
- <pv-button left-icon="plus" is-loading />
165
- ```
166
-
167
- ## Common Mistakes
168
-
169
- | Mistake | Correct Approach |
170
- |---------|------------------|
171
- | Using kebab-case for props | Use PascalCase: `leftIcon` not `left-icon` |
172
- | Importing from barrel exports | Import directly from component path |
173
- | Adding `<style scoped>` blocks | Use pv-* utility classes instead |
174
- | Inline prop types in defineProps | Define interfaces in types.ts |
175
- | Using raw `<button>`, `<input>` | Use PvButton, PvInput components |
176
- | Guessing icon names | Verify at https://pitviper.turquoise.health/visual-style/icons/ |
177
-
178
- ## Styling
179
-
180
- Use only Pit Viper utility classes. No custom CSS.
181
-
182
- ```vue
183
- <!-- Correct -->
184
- <div class="pv-flex pv-stack-16">
185
- <h1 class="pv-heading-1">Title</h1>
186
- <p class="pv-text-body-md pv-text-subdued">Description</p>
187
- </div>
188
-
189
- <!-- Incorrect -->
190
- <div style="display: flex; margin-bottom: 16px;">
191
- <h1 style="font-size: 20px;">Title</h1>
192
- </div>
193
- ```
194
-
195
- ## Tables & Charts
196
-
197
- ### PvDataTable
198
-
199
- Use for all tabular data. Never use raw `<table>` elements or custom table markup.
200
-
201
- ```typescript
202
- import { PvDataTable } from '@turquoisehealth/pit-viper/components/visualizations'
203
-
204
- const colDefs = [
205
- { field: 'name', headerName: 'Name', flex: 1 },
206
- { field: 'email', headerName: 'Email', flex: 1 },
207
- { field: 'role', headerName: 'Role', width: 120 },
208
- ]
209
-
210
- const rowData = ref([
211
- { name: 'Alice', email: 'alice@example.com', role: 'Admin' },
212
- { name: 'Bob', email: 'bob@example.com', role: 'Editor' },
213
- ])
214
- ```
215
-
216
- ```vue
217
- <PvDataTable
218
- :rowData="rowData"
219
- :colDefs="colDefs"
220
- :loading="isLoading"
221
- style="height: 400px;"
222
- />
223
- ```
224
-
225
- ### PvDataTableWithChart
226
-
227
- Use when you need a table with an integrated chart visualization.
228
-
229
- ```typescript
230
- import { PvDataTableWithChart } from '@turquoisehealth/pit-viper/components/visualizations'
231
- ```
232
-
233
- ### PvChart
234
-
235
- Use for standalone charts. Never use D3, Chart.js, ApexCharts, or other charting libraries.
236
-
237
- ```typescript
238
- import { PvChart } from '@turquoisehealth/pit-viper/components/visualizations'
239
-
240
- const chartOptions = {
241
- data: [
242
- { month: 'Jan', revenue: 50000 },
243
- { month: 'Feb', revenue: 62000 },
244
- { month: 'Mar', revenue: 58000 },
245
- ],
246
- series: [
247
- {
248
- type: 'bar',
249
- xKey: 'month',
250
- yKey: 'revenue',
251
- yName: 'Revenue',
252
- },
253
- ],
254
- }
255
- ```
256
-
257
- ```vue
258
- <PvChart :options="chartOptions" style="height: 300px;" />
259
- ```
260
-
261
- ### Table & Chart Mistakes
262
-
263
- | Mistake | Correct Approach |
264
- |---------|------------------|
265
- | Using raw `<table>` elements | Use PvDataTable component |
266
- | Importing D3, Chart.js, ApexCharts | Use PvChart component |
267
- | Custom table CSS/styling | PvDataTable has built-in Pit Viper theme |
268
- | Building custom sorting/filtering | PvDataTable has AG Grid features built-in |
269
-
270
- ## Web Component Compatibility
271
-
272
- If the component may be used as a web component:
273
-
274
- - All non-primitive props (not string/number/boolean) must be optional
275
- - Boolean props default to `false`
276
- - Use `::slotted()` in styles for Shadow DOM
277
- - No scoped or conditional slots (`v-if="$slots.name"`)
278
- - Complex props must be reactive for post-initialization assignment
279
-
280
- ---
281
-
282
- ## Anti-Patterns
283
-
284
- ### Custom Classes on Components
285
-
286
- Don't invent classes. Use pv-* utilities or inline styles for one-offs.
287
-
288
- ```vue
289
- <!-- BAD: Custom classes -->
290
- <template>
291
- <div class="settings-container">
292
- <PvCard class="settings-card">
293
- <PvButton class="submit-btn" label="Save" />
294
- </PvCard>
295
- </div>
296
- </template>
297
-
298
- <style scoped>
299
- .settings-container { padding: 24px; }
300
- .settings-card { max-width: 500px; }
301
- .submit-btn { margin-top: 16px; }
302
- </style>
303
-
304
- <!-- GOOD: Pit Viper classes + inline for one-offs -->
305
- <template>
306
- <div class="pv-inset-square-24">
307
- <PvCard style="max-width: 500px;">
308
- <div class="pv-stack-16">
309
- <PvButton label="Save" />
310
- </div>
311
- </PvCard>
312
- </div>
313
- </template>
314
- ```
315
-
316
- ### Custom CSS / SCSS
317
-
318
- Never add `<style>` blocks with custom styling. This defeats the design system.
319
-
320
- ```vue
321
- <!-- BAD: Custom SCSS -->
322
- <template>
323
- <div class="error-label">
324
- <PvIcon name="info-circle" />
325
- <span class="error-label--text">{{ message }}</span>
326
- </div>
327
- </template>
328
-
329
- <style lang="scss" scoped>
330
- .error-label {
331
- color: $red-base;
332
- display: flex;
333
- gap: 8px;
334
-
335
- &--text {
336
- @include text-medium;
337
- }
338
- }
339
- </style>
340
-
341
- <!-- GOOD: Pit Viper classes only -->
342
- <template>
343
- <div class="pv-flex pv-text-critical" style="--flex-gap: 0.5rem;">
344
- <PvIcon name="info-circle" />
345
- <span class="pv-text-body-md">{{ message }}</span>
346
- </div>
347
- </template>
348
- ```
349
-
350
- ### Raw Tables with Custom Styling
351
-
352
- Never build custom tables. Use PvDataTable.
353
-
354
- ```vue
355
- <!-- BAD: Raw table with custom styling -->
356
- <template>
357
- <table class="my-table">
358
- <thead>
359
- <tr>
360
- <th>Name</th>
361
- <th>Email</th>
362
- </tr>
363
- </thead>
364
- <tbody>
365
- <tr v-for="user in users" :key="user.id">
366
- <td>{{ user.name }}</td>
367
- <td>{{ user.email }}</td>
368
- </tr>
369
- </tbody>
370
- </table>
371
- </template>
372
-
373
- <style scoped>
374
- .my-table { width: 100%; border-collapse: collapse; }
375
- .my-table th { background: #f5f5f5; padding: 8px; }
376
- .my-table td { border-bottom: 1px solid #eee; padding: 8px; }
377
- </style>
378
-
379
- <!-- GOOD: PvDataTable -->
380
- <script setup lang="ts">
381
- import { PvDataTable } from "@turquoisehealth/pit-viper/components/visualizations";
382
-
383
- const colDefs = [
384
- { field: "name", headerName: "Name", flex: 1 },
385
- { field: "email", headerName: "Email", flex: 1 },
386
- ];
387
- </script>
388
-
389
- <template>
390
- <PvDataTable :rowData="users" :colDefs="colDefs" style="height: 400px;" />
391
- </template>
392
- ```
393
-
394
- ---
395
-
396
- ## Good Patterns
397
-
398
- ### Modal with Proper Structure
399
-
400
- ```vue
401
- <script setup lang="ts">
402
- import { PvButton, PvModal } from "@turquoisehealth/pit-viper/components";
403
- import { ref } from "vue";
404
-
405
- const showModal = defineModel<boolean>({ required: true });
406
-
407
- const emit = defineEmits<{
408
- (e: "confirm"): void;
409
- }>();
410
-
411
- const close = () => {
412
- showModal.value = false;
413
- };
414
- </script>
415
-
416
- <template>
417
- <PvModal
418
- v-model="showModal"
419
- header="Delete Report?"
420
- @close="close"
421
- style="--flow-size: 16px; width: 298px"
422
- >
423
- <template #body>
424
- <p class="pv-text-body-sm pv-text-subdued">
425
- This action cannot be undone.
426
- </p>
427
- </template>
428
- <template #footer>
429
- <div class="pv-flex" style="--flex-justify: flex-end;">
430
- <PvButton variant="ghost" @click="close" label="Cancel" size="lg" />
431
- <PvButton variant="destructive" @click="emit('confirm')" label="Delete" size="lg" />
432
- </div>
433
- </template>
434
- </PvModal>
435
- </template>
436
- ```
437
-
438
- **What makes this good:**
439
- - Uses `label` attribute on buttons
440
- - Proper modal structure with `#body` and `#footer` slots
441
- - Semantic `variant="destructive"` for delete action
442
- - Inline style for one-off width constraint
443
- - CSS custom property `--flow-size` instead of custom class
444
-
445
- ### Form Actions with Feedback
446
-
447
- ```vue
448
- <script setup lang="ts">
449
- import { PvButton, PvToast } from "@turquoisehealth/pit-viper/components";
450
- import { ref } from "vue";
451
-
452
- const submitting = ref(false);
453
- const submitError = ref<string | null>(null);
454
- const submitSuccess = ref(false);
455
- </script>
456
-
457
- <template>
458
- <div class="pv-flex-vertical pv-full-width">
459
- <div class="pv-space-between pv-inset-block-24 pv-full-width">
460
- <PvButton size="xl" label="Cancel" variant="secondary" @click="cancel" />
461
- <PvButton
462
- size="xl"
463
- label="Save"
464
- @click="submit"
465
- :disabled="submitting"
466
- :loading="submitting"
467
- />
468
- </div>
469
- <PvToast
470
- v-if="submitSuccess"
471
- icon="check-circle"
472
- label="Saved successfully!"
473
- variant="info"
474
- />
475
- <PvToast
476
- v-if="submitError"
477
- icon="alert-circle"
478
- :label="submitError"
479
- variant="error"
480
- />
481
- </div>
482
- </template>
483
- ```
484
-
485
- **What makes this good:**
486
- - Uses `pv-space-between` for button layout
487
- - Uses `pv-inset-block-24` for vertical padding
488
- - Uses `:loading` prop instead of custom spinner
489
- - Uses `PvToast` for feedback instead of custom alerts
490
-
491
- ### Card with Content Sections
492
-
493
- ```vue
494
- <template>
495
- <PvCard style="max-width: 500px;">
496
- <template #header>
497
- <h2 class="pv-heading-3">User Settings</h2>
498
- </template>
499
-
500
- <div class="pv-flex-vertical" style="--flex-gap: 1rem;">
501
- <div>
502
- <label class="pv-text-title-sm pv-stack-4">Name</label>
503
- <PvInput v-model="name" placeholder="Full name" />
504
- </div>
505
- <div>
506
- <label class="pv-text-title-sm pv-stack-4">Email</label>
507
- <PvInput v-model="email" type="email" />
508
- </div>
509
- </div>
510
-
511
- <template #footer>
512
- <div class="pv-flex" style="--flex-justify: flex-end; --flex-gap: 0.5rem;">
513
- <PvButton variant="ghost" label="Cancel" @click="cancel" />
514
- <PvButton label="Save" @click="save" />
515
- </div>
516
- </template>
517
- </PvCard>
518
- </template>
519
- ```
520
-
521
- **What makes this good:**
522
- - Uses proper card slots (`#header`, default, `#footer`)
523
- - Uses `--flex-gap` CSS custom property instead of custom class
524
- - Uses `--flex-justify` for alignment
525
- - Inline style for max-width constraint (one-off)
526
- - Primary action on the right