@citizenplane/pimp 18.23.0 → 18.23.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.
Files changed (36) hide show
  1. package/dist/components/CpAncillaryItem.vue.d.ts +4 -0
  2. package/dist/components/CpAncillaryItem.vue.d.ts.map +1 -1
  3. package/dist/components/CpStepper.vue.d.ts.map +1 -1
  4. package/dist/pimp.es.js +1751 -1705
  5. package/dist/pimp.umd.js +40 -40
  6. package/dist/style.css +1 -1
  7. package/package.json +1 -1
  8. package/src/assets/images/ancillaries/airport-transfer.png +0 -0
  9. package/src/assets/images/ancillaries/bicycle.png +0 -0
  10. package/src/assets/images/ancillaries/cabin-bag.png +0 -0
  11. package/src/assets/images/ancillaries/checked-bag-size-1.png +0 -0
  12. package/src/assets/images/ancillaries/checked-bag-size-2.png +0 -0
  13. package/src/assets/images/ancillaries/checked-bag-size-3.png +0 -0
  14. package/src/assets/images/ancillaries/checked-bag.png +0 -0
  15. package/src/assets/images/ancillaries/connectivity.png +0 -0
  16. package/src/assets/images/ancillaries/diving-set.png +0 -0
  17. package/src/assets/images/ancillaries/extra-weight.png +0 -0
  18. package/src/assets/images/ancillaries/fast-track.png +0 -0
  19. package/src/assets/images/ancillaries/firearm.png +0 -0
  20. package/src/assets/images/ancillaries/fishing-rod.png +0 -0
  21. package/src/assets/images/ancillaries/golf.png +0 -0
  22. package/src/assets/images/ancillaries/hand-bag.png +0 -0
  23. package/src/assets/images/ancillaries/kite-surf.png +0 -0
  24. package/src/assets/images/ancillaries/lounge-access.png +0 -0
  25. package/src/assets/images/ancillaries/meet-and-greet.png +0 -0
  26. package/src/assets/images/ancillaries/music.png +0 -0
  27. package/src/assets/images/ancillaries/oversized.png +0 -0
  28. package/src/assets/images/ancillaries/pet-in-cabin.png +0 -0
  29. package/src/assets/images/ancillaries/pet-in-hold.png +0 -0
  30. package/src/assets/images/ancillaries/skis.png +0 -0
  31. package/src/assets/images/ancillaries/special-meal.png +0 -0
  32. package/src/assets/images/ancillaries/standard-meal.png +0 -0
  33. package/src/components/CpAncillaryItem.vue +165 -7
  34. package/src/components/CpStepper.vue +47 -11
  35. package/src/stories/CpAncillaryItem.stories.ts +212 -6
  36. package/src/stories/CpStepper.stories.ts +50 -1
@@ -8,10 +8,43 @@ import { docCellStyle, docLabelStyle, docRowWrapStyle } from '@/stories/document
8
8
 
9
9
  const galleryCellStyle = `${docCellStyle} width: 260px;`
10
10
 
11
+ const listStackRows = [
12
+ {
13
+ title: 'Hand bag',
14
+ description: '5kg • 45 × 36 × 20 cm',
15
+ type: CpAncillaryItemTypes.HAND_BAG,
16
+ price: 990,
17
+ quantity: 0,
18
+ },
19
+ {
20
+ title: 'Cabin bag',
21
+ description: '10kg • 55 × 36 × 25 cm',
22
+ type: CpAncillaryItemTypes.CABIN_BAG,
23
+ price: 1990,
24
+ quantity: 1,
25
+ },
26
+ {
27
+ title: 'Checked bag',
28
+ description: '23kg • 158 cm total',
29
+ type: CpAncillaryItemTypes.CHECKED_BAG,
30
+ price: 2990,
31
+ quantity: 0,
32
+ },
33
+ ]
34
+
35
+ const listCompactRows = listStackRows.slice(0, 2)
36
+
37
+ const listWrapStyle = 'padding: 20px 0; width: 358px;'
38
+ const listCompactWrapStyle = 'padding: 20px 0; width: 21rem;'
39
+
11
40
  const meta = {
12
41
  title: 'Business/CpAncillaryItem',
13
42
  component: CpAncillaryItem,
14
43
  argTypes: {
44
+ addLabel: {
45
+ control: 'text',
46
+ description: 'Label of the button shown while the quantity is 0',
47
+ },
15
48
  currency: {
16
49
  control: 'text',
17
50
  description: 'ISO currency code used to format price',
@@ -20,6 +53,11 @@ const meta = {
20
53
  control: 'text',
21
54
  description: 'Description text for the ancillary',
22
55
  },
56
+ layout: {
57
+ control: 'select',
58
+ options: ['card', 'list'],
59
+ description: 'Card renders the bordered tile, list renders a separated row',
60
+ },
23
61
  locale: {
24
62
  control: 'text',
25
63
  description: 'Locale used to format price, defaults to the browser locale',
@@ -60,7 +98,8 @@ export default meta
60
98
  type Story = StoryObj<typeof meta>
61
99
 
62
100
  /**
63
- * Default ancillary card. Use the controls to experiment with each prop in isolation.
101
+ * Default ancillary card. It starts on the Add button, which sets the quantity to 1
102
+ * and hands over to the counter. Use the controls to experiment with each prop in isolation.
64
103
  */
65
104
  export const Default: Story = {
66
105
  args: {
@@ -93,7 +132,8 @@ export const Default: Story = {
93
132
  /* -------------------------------------------------------------------------- */
94
133
 
95
134
  /**
96
- * quantity > 0 adds the accent ring and swaps the counter background.
135
+ * quantity > 0 replaces the Add button with the counter, adds the accent ring and
136
+ * swaps the counter background.
97
137
  */
98
138
  export const Selected: Story = {
99
139
  args: {
@@ -115,7 +155,7 @@ export const Selected: Story = {
115
155
  }
116
156
 
117
157
  /**
118
- * No maxQuantity: the counter has no upper bound and the caption is hidden.
158
+ * No maxQuantity: once added, the counter has no upper bound and the caption is hidden.
119
159
  */
120
160
  export const Uncapped: Story = {
121
161
  args: {
@@ -227,6 +267,159 @@ export const VerticalOnMobile: Story = {
227
267
  }),
228
268
  }
229
269
 
270
+ /* -------------------------------------------------------------------------- */
271
+ /* List layout */
272
+ /* -------------------------------------------------------------------------- */
273
+
274
+ /**
275
+ * The list layout: no card frame, a 44px visual, title and price on one line and
276
+ * a soft bottom separator so stacked rows read as a list. maxQuantityLabel is not
277
+ * rendered here.
278
+ */
279
+ export const List: Story = {
280
+ args: {
281
+ title: 'Hand bag',
282
+ description: '5kg • 45 × 36 × 20 cm',
283
+ type: CpAncillaryItemTypes.HAND_BAG,
284
+ quantity: 0,
285
+ maxQuantity: 10,
286
+ maxQuantityLabel: 'max. 10',
287
+ price: 990,
288
+ currency: 'EUR',
289
+ layout: 'list',
290
+ },
291
+ render: (args) => ({
292
+ components: { CpAncillaryItem },
293
+ setup() {
294
+ const quantity = ref(0)
295
+ return { args, quantity, listWrapStyle }
296
+ },
297
+ template: `
298
+ <div :style="listWrapStyle">
299
+ <CpAncillaryItem v-bind="args" v-model:quantity="quantity" />
300
+ </div>
301
+ `,
302
+ }),
303
+ }
304
+
305
+ /**
306
+ * Selected list row: the counter replaces the Add button and the row keeps its
307
+ * plain background, without the accent ring the card gets.
308
+ */
309
+ export const ListSelected: Story = {
310
+ args: {
311
+ ...List.args,
312
+ quantity: 1,
313
+ },
314
+ render: (args) => ({
315
+ components: { CpAncillaryItem },
316
+ setup() {
317
+ const quantity = ref(1)
318
+ return { args, quantity, listWrapStyle }
319
+ },
320
+ template: `
321
+ <div :style="listWrapStyle">
322
+ <CpAncillaryItem v-bind="args" v-model:quantity="quantity" />
323
+ </div>
324
+ `,
325
+ }),
326
+ }
327
+
328
+ /**
329
+ * Below the tablet breakpoint the title, the price and the description stack on
330
+ * three lines and the bullet disappears.
331
+ */
332
+ export const ListOnMobile: Story = {
333
+ args: {
334
+ ...List.args,
335
+ },
336
+ globals: {
337
+ viewport: { value: '390-844', isRotated: false },
338
+ },
339
+ render: (args) => ({
340
+ components: { CpAncillaryItem },
341
+ setup() {
342
+ const quantity = ref(0)
343
+ return { args, quantity, listWrapStyle }
344
+ },
345
+ template: `
346
+ <div :style="listWrapStyle">
347
+ <CpAncillaryItem v-bind="args" v-model:quantity="quantity" />
348
+ </div>
349
+ `,
350
+ }),
351
+ }
352
+
353
+ /**
354
+ * Under 22rem of component width the row goes compact: a 4px gap to a 40px visual
355
+ * holding a 24px image, the details wrap with an 8px gap, and the description drops
356
+ * to 12px. Driven by the component's own width, so a 336px side panel switches while
357
+ * the wider list does not.
358
+ */
359
+ export const ListCompact: Story = {
360
+ args: {
361
+ ...List.args,
362
+ },
363
+ parameters: { controls: { disable: true } },
364
+ render: () => ({
365
+ components: { CpAncillaryItem },
366
+ setup() {
367
+ const rows = listCompactRows
368
+ const quantities = ref(Object.fromEntries(rows.map((row) => [row.type, row.quantity])))
369
+ return { rows, quantities, listCompactWrapStyle }
370
+ },
371
+ template: `
372
+ <div :style="listCompactWrapStyle">
373
+ <CpAncillaryItem
374
+ v-for="row in rows"
375
+ :key="row.type"
376
+ v-model:quantity="quantities[row.type]"
377
+ :title="row.title"
378
+ :description="row.description"
379
+ :type="row.type"
380
+ :price="row.price"
381
+ currency="EUR"
382
+ layout="list"
383
+ />
384
+ </div>
385
+ `,
386
+ }),
387
+ }
388
+
389
+ /**
390
+ * Several rows in a row, to show how the separators build a list. The last row drops
391
+ * its separator so the list does not end on a rule.
392
+ */
393
+ export const ListStack: Story = {
394
+ args: {
395
+ ...List.args,
396
+ },
397
+ parameters: { controls: { disable: true } },
398
+ render: () => ({
399
+ components: { CpAncillaryItem },
400
+ setup() {
401
+ const rows = listStackRows
402
+ const quantities = ref(Object.fromEntries(rows.map((row) => [row.type, row.quantity])))
403
+ return { rows, quantities, listWrapStyle }
404
+ },
405
+ template: `
406
+ <div :style="listWrapStyle">
407
+ <CpAncillaryItem
408
+ v-for="row in rows"
409
+ :key="row.type"
410
+ v-model:quantity="quantities[row.type]"
411
+ :title="row.title"
412
+ :description="row.description"
413
+ :type="row.type"
414
+ :price="row.price"
415
+ currency="EUR"
416
+ layout="list"
417
+ />
418
+ </div>
419
+ `,
420
+ }),
421
+ }
422
+
230
423
  /* -------------------------------------------------------------------------- */
231
424
  /* Gallery */
232
425
  /* -------------------------------------------------------------------------- */
@@ -244,15 +437,27 @@ export const Gallery: Story = {
244
437
  components: { CpAncillaryItem },
245
438
  setup() {
246
439
  const types = Object.values(CpAncillaryItemTypes)
247
- const quantities = ref(Object.fromEntries(types.map((type) => [type, 0])))
248
- return { types, quantities, galleryCellStyle, docLabelStyle, docRowWrapStyle }
440
+ const cardQuantities = ref(Object.fromEntries(types.map((type) => [type, 0])))
441
+ const listQuantities = ref(Object.fromEntries(types.map((type) => [type, 0])))
442
+ return { types, cardQuantities, listQuantities, galleryCellStyle, docLabelStyle, docRowWrapStyle }
249
443
  },
250
444
  template: `
251
445
  <div :style="docRowWrapStyle">
252
446
  <div v-for="type in types" :key="type" :style="galleryCellStyle">
253
447
  <span :style="docLabelStyle">{{ type }}</span>
254
448
  <CpAncillaryItem
255
- v-model:quantity="quantities[type]"
449
+ v-model:quantity="cardQuantities[type]"
450
+ :title="type"
451
+ description="Sample description"
452
+ :type="type"
453
+ :max-quantity="5"
454
+ max-quantity-label="max. 5"
455
+ :price="1000"
456
+ currency="EUR"
457
+ />
458
+ <span :style="docLabelStyle">{{ type }} — list</span>
459
+ <CpAncillaryItem
460
+ v-model:quantity="listQuantities[type]"
256
461
  :title="type"
257
462
  description="Sample description"
258
463
  :type="type"
@@ -260,6 +465,7 @@ export const Gallery: Story = {
260
465
  max-quantity-label="max. 5"
261
466
  :price="1000"
262
467
  currency="EUR"
468
+ layout="list"
263
469
  />
264
470
  </div>
265
471
  </div>
@@ -37,6 +37,10 @@ export default meta
37
37
  type Story = StoryObj<typeof meta>
38
38
 
39
39
  /**
40
+ * Three visual states coexist: steps before the active one render as
41
+ * completed (white pill, green check badge, dark label), the active one keeps
42
+ * its accent pill, and the following ones stay upcoming (bordered, grey).
43
+ *
40
44
  * Clicking an enabled step updates the active step locally, the way a
41
45
  * consumer would react to `select` by navigating.
42
46
  */
@@ -65,10 +69,55 @@ export const Default: Story = {
65
69
  }),
66
70
  }
67
71
 
72
+ /**
73
+ * Three steps already behind the active one: each renders completed, with the
74
+ * check badge replacing its number. Completed steps stay clickable.
75
+ */
76
+ export const Completed: Story = {
77
+ args: {
78
+ steps,
79
+ activeStepId: 'extras',
80
+ reachedStepIndex: 3,
81
+ },
82
+ render: (args) => ({
83
+ components: { CpStepper },
84
+ setup() {
85
+ return { args }
86
+ },
87
+ template: `
88
+ <div style="width: 70rem;">
89
+ <CpStepper v-bind="args" />
90
+ </div>
91
+ `,
92
+ }),
93
+ }
94
+
95
+ /**
96
+ * The funnel's last step is active, so every preceding step is completed.
97
+ */
98
+ export const LastStep: Story = {
99
+ args: {
100
+ steps,
101
+ activeStepId: 'confirmation',
102
+ reachedStepIndex: 4,
103
+ },
104
+ render: (args) => ({
105
+ components: { CpStepper },
106
+ setup() {
107
+ return { args }
108
+ },
109
+ template: `
110
+ <div style="width: 70rem;">
111
+ <CpStepper v-bind="args" />
112
+ </div>
113
+ `,
114
+ }),
115
+ }
116
+
68
117
  /**
69
118
  * Below the container width threshold (60.5rem, matching online-checkin's
70
119
  * former `$breakpoint-large-tablet`), step titles collapse and only the
71
- * numbered pills remain.
120
+ * numbered pills remain, the completed ones showing their check badge.
72
121
  */
73
122
  export const Narrow: Story = {
74
123
  args: { ...Default.args },