sprintify-ui 0.11.16 → 0.11.18

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,4 +1,4 @@
1
- import { GanttRow, GanttRowFormatted, GanttItemFormatted } from '@/services/gantt/types';
1
+ import { GanttRow, GanttRowFormatted, GanttItemFormatted, GanttRelationship } from '@/services/gantt/types';
2
2
  import { VNode } from 'vue';
3
3
  interface Props {
4
4
  /**
@@ -25,6 +25,10 @@ interface Props {
25
25
  * Flatten the Gantt chart by removing the hierarchy of rows and displaying all items in a single list. This is useful when there's only one level of rows and you want to maximize the vertical space.
26
26
  */
27
27
  flatten?: boolean;
28
+ /**
29
+ * Relationships between items, drawn as arrows from one item to another.
30
+ */
31
+ relationships?: GanttRelationship[];
28
32
  }
29
33
  type __VLS_Slots = {
30
34
  sidebarRow: (props: {
@@ -51,6 +55,7 @@ declare const __VLS_component: import("vue").DefineComponent<Props, {}, {}, {},
51
55
  sidebarWidth: number;
52
56
  rowHeight: number;
53
57
  includeToday: boolean;
58
+ relationships: GanttRelationship[];
54
59
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
55
60
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
56
61
  export default _default;
@@ -1,8 +1,8 @@
1
1
  import { UseFloatingOptions } from '@floating-ui/vue';
2
2
  type __VLS_Props = {
3
+ as?: string;
3
4
  visible?: boolean;
4
5
  text?: string | null | undefined;
5
- class?: string[] | string | null | undefined;
6
6
  floatingOptions?: UseFloatingOptions;
7
7
  /**
8
8
  * Whether the tooltip content is interactive or not.
@@ -13,16 +13,16 @@ type __VLS_Props = {
13
13
  dark?: boolean;
14
14
  offset?: number;
15
15
  };
16
- declare var __VLS_1: {}, __VLS_11: {};
16
+ declare var __VLS_7: {}, __VLS_17: {};
17
17
  type __VLS_Slots = {} & {
18
- default?: (props: typeof __VLS_1) => any;
18
+ default?: (props: typeof __VLS_7) => any;
19
19
  } & {
20
- tooltip?: (props: typeof __VLS_11) => any;
20
+ tooltip?: (props: typeof __VLS_17) => any;
21
21
  };
22
22
  declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
23
23
  dark: boolean;
24
- class: string | string[] | null;
25
24
  text: string | null;
25
+ as: string;
26
26
  visible: boolean;
27
27
  floatingOptions: UseFloatingOptions;
28
28
  interactive: boolean;
@@ -80,3 +80,7 @@ export interface Scale {
80
80
  export interface NowLine {
81
81
  x: number;
82
82
  }
83
+ export interface GanttRelationship {
84
+ fromId: number | string;
85
+ toId: number | string;
86
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sprintify-ui",
3
- "version": "0.11.16",
3
+ "version": "0.11.18",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "rimraf dist && vue-tsc && vite build",
@@ -157,4 +157,78 @@ const SlotSidebarSlotsTemplate = (args) => ({
157
157
  });
158
158
 
159
159
  export const SlotSidebarSlots = SlotSidebarSlotsTemplate.bind({});
160
- SlotSidebarSlots.args = {};
160
+ SlotSidebarSlots.args = {};
161
+
162
+ // Relationships story with unique item IDs
163
+
164
+ const relationshipRows = [];
165
+
166
+ let itemId = 1;
167
+
168
+ for (let i = 0; i < 5; i++) {
169
+ const items = [];
170
+
171
+ for (let j = 0; j < 2; j++) {
172
+ const start = DateTime.now()
173
+ .minus({ days: 30 })
174
+ .plus({ days: (i * 15) + (j * 20) });
175
+
176
+ const end = start.plus({ days: 15 });
177
+
178
+ const colors = ["blue", "green", "red", "indigo", "purple"];
179
+ const color = defaultColors[colors[i % colors.length]][500];
180
+
181
+ items.push({
182
+ id: itemId++,
183
+ name: `Task ${items.length + 1} of Project ${i + 1}`,
184
+ start: start.toISO(),
185
+ end: end.toISO(),
186
+ color: color,
187
+ });
188
+ }
189
+
190
+ relationshipRows.push({
191
+ id: i + 1,
192
+ name: `Project ${i + 1}`,
193
+ items: items,
194
+ height: 30,
195
+ });
196
+ }
197
+
198
+ const relationships = [
199
+ { fromId: 1, toId: 3 },
200
+ { fromId: 3, toId: 5 },
201
+ { fromId: 2, toId: 6 },
202
+ { fromId: 5, toId: 9 },
203
+ ];
204
+
205
+ const RelationshipsTemplate = (args) => ({
206
+ components: {
207
+ BaseCard,
208
+ BaseGantt,
209
+ },
210
+ setup() {
211
+ function onItemClick(item) {
212
+ alert(`Item "${item.name}" clicked`);
213
+ }
214
+
215
+ function onRowClick(item) {
216
+ alert(`Row "${item.name}" clicked`);
217
+ }
218
+
219
+ return { args, onItemClick, onRowClick };
220
+ },
221
+ template: `
222
+ <BaseCard clipped>
223
+ <BaseGantt v-bind="args" @item:click="onItemClick" @row:click="onRowClick">
224
+ </BaseGantt>
225
+ </BaseCard>
226
+ `,
227
+ });
228
+
229
+ export const Relationships = RelationshipsTemplate.bind({});
230
+ Relationships.args = {
231
+ rows: relationshipRows,
232
+ relationships,
233
+ maxHeight: 500,
234
+ };
@@ -276,6 +276,45 @@
276
276
  </div>
277
277
  </div>
278
278
  </li>
279
+
280
+ <!-- Relationship arrows -->
281
+
282
+ <svg
283
+ v-if="relationshipPaths.length"
284
+ class="absolute top-0 left-0 pointer-events-none"
285
+ :viewBox="`0 0 ${width} ${height}`"
286
+ :width="width"
287
+ :height="height"
288
+ >
289
+ <defs>
290
+ <marker
291
+ id="gantt-arrowhead"
292
+ markerWidth="8"
293
+ markerHeight="6"
294
+ refX="8"
295
+ refY="3"
296
+ orient="auto"
297
+ >
298
+ <polygon
299
+ points="0 0, 8 3, 0 6"
300
+ fill="black"
301
+ ></polygon>
302
+ </marker>
303
+ </defs>
304
+ <g
305
+ v-for="rel in relationshipPaths"
306
+ :key="rel.id"
307
+ opacity="0.3"
308
+ >
309
+ <path
310
+ :d="rel.path"
311
+ fill="none"
312
+ stroke="black"
313
+ stroke-width="1.5"
314
+ marker-end="url(#gantt-arrowhead)"
315
+ ></path>
316
+ </g>
317
+ </svg>
279
318
  </ul>
280
319
 
281
320
  <!-- Vertical lines -->
@@ -346,7 +385,7 @@
346
385
  y1="0"
347
386
  :y2="height"
348
387
  stroke="red"
349
- stroke-width="2"
388
+ stroke-width="1.5"
350
389
  style="filter: drop-shadow(0px 0px 2px rgb(0 0 0 / 0.1));"
351
390
  ></line>
352
391
  </svg>
@@ -358,7 +397,7 @@
358
397
  <script lang="ts" setup>
359
398
  import { useElementSize, useScroll } from '@vueuse/core';
360
399
  import { Format } from '@/services/gantt/format';
361
- import { FormatConfig, GanttRow, GanttRowFormatted, Group, Tick, NowLine, GanttItemFormatted } from '@/services/gantt/types';
400
+ import { FormatConfig, GanttRow, GanttRowFormatted, Group, Tick, NowLine, GanttItemFormatted, GanttRelationship } from '@/services/gantt/types';
362
401
  import { cloneDeep, debounce } from 'lodash';
363
402
  import { slate } from 'tailwindcss/colors';
364
403
  import { VNode } from 'vue';
@@ -388,6 +427,10 @@ interface Props {
388
427
  * Flatten the Gantt chart by removing the hierarchy of rows and displaying all items in a single list. This is useful when there's only one level of rows and you want to maximize the vertical space.
389
428
  */
390
429
  flatten?: boolean;
430
+ /**
431
+ * Relationships between items, drawn as arrows from one item to another.
432
+ */
433
+ relationships?: GanttRelationship[];
391
434
  }
392
435
 
393
436
  const props = withDefaults(defineProps<Props>(), {
@@ -395,6 +438,7 @@ const props = withDefaults(defineProps<Props>(), {
395
438
  rowHeight: 40,
396
439
  maxHeight: undefined,
397
440
  includeToday: true,
441
+ relationships: () => [],
398
442
  });
399
443
 
400
444
  defineEmits<{
@@ -521,4 +565,52 @@ function isRowExpanded(rowId: string | number): boolean {
521
565
  return expandedRows.value.has(rowId);
522
566
  }
523
567
 
568
+ // Relationship arrows
569
+
570
+ const relationshipPaths = computed(() => {
571
+ if (!props.relationships.length || !rowsInternal.value.length) return [];
572
+
573
+ const positions = new Map<string | number, { x: number; centerY: number; width: number }>();
574
+ let cumulativeY = 0;
575
+
576
+ for (const row of rowsInternal.value) {
577
+ if (!props.flatten) {
578
+ cumulativeY += row.height;
579
+ }
580
+
581
+ if (isRowExpanded(row.id)) {
582
+ for (const item of row.items) {
583
+ positions.set(item.id, {
584
+ x: item.x,
585
+ centerY: cumulativeY + item.y + item.barHeight / 2,
586
+ width: item.width,
587
+ });
588
+ cumulativeY += item.height;
589
+ }
590
+ }
591
+ }
592
+
593
+ return props.relationships
594
+ .map((rel) => {
595
+ const from = positions.get(rel.fromId);
596
+ const to = positions.get(rel.toId);
597
+ if (!from || !to) return null;
598
+
599
+ const startX = from.x + from.width;
600
+ const startY = from.centerY;
601
+ const endX = to.x;
602
+ const endY = to.centerY;
603
+
604
+ const TAIL_LENGTH = 20;
605
+ const curveEndX = endX - TAIL_LENGTH;
606
+ const dx = Math.abs(curveEndX - startX);
607
+ const offset = Math.max(20, dx * 0.3);
608
+
609
+ const path = `M ${startX} ${startY} C ${startX + offset} ${startY}, ${curveEndX - offset} ${endY}, ${curveEndX} ${endY} L ${endX} ${endY}`;
610
+
611
+ return { path, id: `${rel.fromId}-${rel.toId}` };
612
+ })
613
+ .filter((r): r is { path: string; id: string } => r !== null);
614
+ });
615
+
524
616
  </script>
@@ -22,7 +22,7 @@ const Template = (args) => ({
22
22
  template: `
23
23
  <BaseCard>
24
24
  <BaseCardRow size=sm>
25
- <BaseTooltip class="inline-block" v-bind="args">
25
+ <BaseTooltip class="inline-block" title="test" v-bind="args">
26
26
  <div>Hover me, the tooltip show appear outside the BaseCard</div>
27
27
  </BaseTooltip>
28
28
  </BaseCardRow>
@@ -1,43 +1,43 @@
1
1
  <template>
2
- <div
2
+ <component
3
+ :is="as"
3
4
  ref="targetRef"
4
- :class="classInternal"
5
5
  >
6
6
  <slot />
7
- </div>
8
- <Teleport
9
- v-if="visible"
10
- to="body"
11
- >
12
- <div
13
- ref="tooltipRef"
14
- class="fixed top-0 left-0 z-tooltip"
15
- :class="[!interactive ? 'pointer-events-none' : '']"
16
- :style="floatingStyles"
7
+ <Teleport
8
+ v-if="visible"
9
+ to="body"
17
10
  >
18
- <transition
19
- enter-active-class="transition duration-200 ease-out"
20
- enter-from-class="transform scale-90 opacity-0"
21
- enter-to-class="transform scale-100 opacity-100"
22
- leave-active-class="transition duration-75 ease-in"
23
- leave-from-class="transform scale-100 opacity-100"
24
- leave-to-class="transform scale-90 opacity-0"
11
+ <div
12
+ ref="tooltipRef"
13
+ class="fixed top-0 left-0 z-tooltip"
14
+ :class="[!interactive ? 'pointer-events-none' : '']"
15
+ :style="floatingStyles"
25
16
  >
26
- <slot
27
- v-if="showTooltip"
28
- name="tooltip"
17
+ <transition
18
+ enter-active-class="transition duration-200 ease-out"
19
+ enter-from-class="transform scale-90 opacity-0"
20
+ enter-to-class="transform scale-100 opacity-100"
21
+ leave-active-class="transition duration-75 ease-in"
22
+ leave-from-class="transform scale-100 opacity-100"
23
+ leave-to-class="transform scale-90 opacity-0"
29
24
  >
30
- <div
31
- class="text-xs max-w-xs leading-snug rounded-md pt-1.5 pb-2 px-3"
32
- :class="[
33
- dark ? 'bg-slate-900 text-white' : 'bg-white text-slate-900 ring-1 ring-black ring-opacity-10 shadow-md',
34
- ]"
35
- v-html="text"
36
- />
37
- </slot>
38
- </transition>
39
- </div>
40
- </Teleport>
25
+ <slot
26
+ v-if="showTooltip"
27
+ name="tooltip"
28
+ >
29
+ <div
30
+ class="text-xs max-w-xs leading-snug rounded-md pt-1.5 pb-2 px-3"
31
+ :class="[
32
+ dark ? 'bg-slate-900 text-white' : 'bg-white text-slate-900 ring-1 ring-black ring-opacity-10 shadow-md',
33
+ ]"
34
+ v-html="text"
35
+ />
36
+ </slot>
37
+ </transition>
38
+ </div>
39
+ </Teleport>
40
+ </component>
41
41
  </template>
42
42
 
43
43
  <script lang="ts" setup>
@@ -45,14 +45,10 @@ import { useTooltip } from '@/composables/tooltip';
45
45
  import { unrefElement } from '@vueuse/core';
46
46
  import { UseFloatingOptions } from '@floating-ui/vue';
47
47
 
48
- defineOptions({
49
- inheritAttrs: false,
50
- });
51
-
52
48
  const props = withDefaults(defineProps<{
49
+ as?: string,
53
50
  visible?: boolean,
54
51
  text?: string | null | undefined;
55
- class?: string[] | string | null | undefined;
56
52
  floatingOptions?: UseFloatingOptions,
57
53
  /**
58
54
  * Whether the tooltip content is interactive or not.
@@ -63,9 +59,9 @@ const props = withDefaults(defineProps<{
63
59
  dark?: boolean,
64
60
  offset?: number,
65
61
  }>(), {
62
+ as: 'div',
66
63
  visible: true,
67
64
  text: null,
68
- class: null,
69
65
  floatingOptions() {
70
66
  return {
71
67
  placement: 'top-start',
@@ -85,10 +81,4 @@ const tooltipRef = ref<HTMLElement | null>(null)
85
81
 
86
82
  const { floatingStyles, showTooltip } = useTooltip(targetInternal, tooltipRef, props.interactive, props.delay, props.floatingOptions, props.offset);
87
83
 
88
- const classInternal = computed(() => {
89
- return [
90
- props.class,
91
- ];
92
- });
93
-
94
84
  </script>
@@ -89,3 +89,8 @@ export interface Scale {
89
89
  export interface NowLine {
90
90
  x: number;
91
91
  }
92
+
93
+ export interface GanttRelationship {
94
+ fromId: number | string;
95
+ toId: number | string;
96
+ }