sprintify-ui 0.11.16 → 0.11.19

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.19",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "rimraf dist && vue-tsc && vite build",
@@ -1,20 +1,53 @@
1
1
  import { Canvas, Meta, Primary, Controls, Story } from '@storybook/addon-docs/blocks';
2
-
2
+
3
3
  import * as BaseGanttStories from './BaseGantt.stories';
4
-
4
+
5
5
  <Meta of={BaseGanttStories} />
6
-
6
+
7
7
  # BaseGantt
8
8
 
9
9
  ## Introduction
10
-
11
- This is the BaseGantt component used to display Gantt charts. It allows you to visualize tasks or events over a timeline.
10
+
11
+ A Gantt chart component for visualizing tasks and events over a timeline. Supports hierarchical rows with nested items, relationship arrows between items, a resizable sidebar, a "now" line, and fully customizable rendering via slots.
12
12
 
13
13
  <Primary />
14
14
 
15
- ## API
15
+ ## Relationships
16
+
17
+ Use the `relationships` prop to draw arrows between items. Each relationship connects a source item to a target item by their IDs.
18
+
19
+ <Canvas of={BaseGanttStories.Relationships} />
20
+
21
+ ## Slots
22
+
23
+ | Slot | Props | Description |
24
+ |---------------|------------------------------------|--------------------------------------------------------------|
25
+ | row | `{ row: GanttRowFormatted }` | Custom rendering for a row bar on the chart |
26
+ | item | `{ item: GanttItemFormatted }` | Custom rendering for an item bar on the chart |
27
+ | sidebarRow | `{ row: GanttRowFormatted }` | Custom rendering for a row entry in the sidebar |
28
+ | sidebarItem | `{ item: GanttItemFormatted }` | Custom rendering for an item entry in the sidebar |
29
+
30
+ <Canvas of={BaseGanttStories.SlotItem} />
31
+ <Canvas of={BaseGanttStories.SlotSidebarSlots} />
32
+
33
+ ## Events
34
+
35
+ | Event | Payload | Description |
36
+ |--------------|------------------------|--------------------------------------|
37
+ | row:click | `GanttRowFormatted` | Emitted when a row bar is clicked |
38
+ | item:click | `GanttItemFormatted` | Emitted when an item bar is clicked |
39
+
40
+ ## Props
41
+
42
+ <Controls />
16
43
 
17
- ### GanttRow
44
+ ## Types
45
+
46
+ ### Input types
47
+
48
+ These types are used when passing data to the component.
49
+
50
+ #### GanttRow
18
51
 
19
52
  | Prop | Type | Description |
20
53
  |-----------|-----------------------------|--------------------------------------------------|
@@ -22,58 +55,60 @@ This is the BaseGantt component used to display Gantt charts. It allows you to v
22
55
  | name | string | Name of the row |
23
56
  | meta | Object? | Optional metadata for the row |
24
57
  | items | GanttItem[] | Array of items (tasks/events) in the row |
25
- | height | number? | Optional custom height for the row. If not provided, props.rowHeight will be used. |
58
+ | height | number? | Optional custom height for the row. If not provided, `rowHeight` prop will be used. |
26
59
 
27
- ### GanttItem
60
+ #### GanttItem
28
61
 
29
62
  | Prop | Type | Description |
30
63
  |-----------|-----------------------------|--------------------------------------------------|
31
64
  | id | number \| string | Unique identifier for the item |
32
65
  | start | string | Start date/time (ISO string) |
33
66
  | end | string | End date/time (ISO string) |
34
- | height | number? | Optional custom height for the item. If not provided, props.rowHeight will be used. |
67
+ | height | number? | Optional custom height for the item. If not provided, `rowHeight` prop will be used. |
35
68
  | name | string | Name of the item |
36
69
  | meta | Object? | Optional metadata for the item |
37
70
  | color | string | Color used to display the item |
38
71
 
39
- ### GanttRowFormatted
40
-
41
- Once the rows have been formatted for rendering, the GanttRowFormatted type is used.
72
+ #### GanttRelationship
42
73
 
43
74
  | Prop | Type | Description |
44
75
  |-----------|-----------------------------|--------------------------------------------------|
45
- | id | number \| string | Unique identifier for the row |
46
- | name | string | Name of the row |
47
- | items | GanttItemFormatted[] | Array of formatted items (tasks/events) in the row |
48
- | height | number | The rendered height of the row |
49
- | barHeight | number | The height of the bars in the row |
50
- | width | number | The width of the bar in pixels |
51
- | start | DateTime | The DateTime start of the bar |
52
- | end | DateTime | The DateTime end of the bar |
53
- | x | number | The x position of the bar in pixels |
54
- | y | number | The y position of the bar in pixels |
55
- | meta | Object? | Optional metadata for the row |
76
+ | fromId | number \| string | ID of the source item (where the arrow starts) |
77
+ | toId | number \| string | ID of the target item (where the arrow ends) |
56
78
 
57
- ### GanttItemFormatted
79
+ ### Formatted types
58
80
 
59
- Once the items have been formatted for rendering, the GanttItemFormatted type is used.
81
+ These types are provided to slot consumers and event handlers after the component has processed the input data for rendering.
82
+
83
+ #### GanttRowFormatted
60
84
 
61
85
  | Prop | Type | Description |
62
86
  |-----------|-----------------------------|--------------------------------------------------|
63
- | id | number \| string | Unique identifier for the item |
64
- | name | string | Name of the item |
65
- | color | string | The color of the item |
66
- | height | number | The rendered height of the item |
67
- | barHeight | number | The height of the bar in the item |
68
- | width | number | The width of the item in pixels |
69
- | start | DateTime | The DateTime start of the item |
70
- | end | DateTime | The DateTime end of the item |
71
- | x | number | The x position of the item in pixels |
72
- | y | number | The y position of the item in pixels |
73
- | milliseconds | number | The duration of the item in milliseconds |
74
- | meta | Object? | Optional metadata for the item |
87
+ | id | number \| string | Unique identifier for the row |
88
+ | name | string | Name of the row |
89
+ | items | GanttItemFormatted[] | Array of formatted items in the row |
90
+ | height | number | The rendered height of the row |
91
+ | barHeight | number | The height of the bars in the row |
92
+ | width | number | The width of the bar in pixels |
93
+ | start | DateTime | The DateTime start of the bar |
94
+ | end | DateTime | The DateTime end of the bar |
95
+ | x | number | The x position of the bar in pixels |
96
+ | y | number | The y position of the bar in pixels |
97
+ | meta | Object? | Optional metadata for the row |
75
98
 
99
+ #### GanttItemFormatted
76
100
 
77
- ## Props
78
-
79
- <Controls />
101
+ | Prop | Type | Description |
102
+ |--------------|--------------------------|--------------------------------------------------|
103
+ | id | number \| string | Unique identifier for the item |
104
+ | name | string | Name of the item |
105
+ | color | string | The color of the item |
106
+ | height | number | The rendered height of the item |
107
+ | barHeight | number | The height of the bar in the item |
108
+ | width | number | The width of the item in pixels |
109
+ | start | DateTime | The DateTime start of the item |
110
+ | end | DateTime | The DateTime end of the item |
111
+ | x | number | The x position of the item in pixels |
112
+ | y | number | The y position of the item in pixels |
113
+ | milliseconds | number | The duration of the item in milliseconds |
114
+ | meta | Object? | Optional metadata for the item |
@@ -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
+ }