sprintify-ui 0.2.10 → 0.2.12

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 (35) hide show
  1. package/dist/sprintify-ui.es.js +3731 -3727
  2. package/dist/types/src/components/BaseActionItem.vue.d.ts +27 -20
  3. package/dist/types/src/components/BaseActionItemButton.vue.d.ts +21 -28
  4. package/dist/types/src/components/BaseAddressForm.vue.d.ts +63 -50
  5. package/dist/types/src/components/BaseBadge.vue.d.ts +41 -40
  6. package/dist/types/src/components/BaseBoolean.vue.d.ts +9 -14
  7. package/dist/types/src/components/BaseCropper.vue.d.ts +42 -25
  8. package/dist/types/src/components/BaseCropperModal.vue.d.ts +18 -17
  9. package/dist/types/src/components/BaseDataIteratorSectionBox.vue.d.ts +11 -14
  10. package/dist/types/src/components/BaseDataIteratorSectionButton.vue.d.ts +12 -15
  11. package/dist/types/src/components/BaseDataIteratorSectionModal.vue.d.ts +20 -17
  12. package/dist/types/src/components/BaseDataTableRowAction.vue.d.ts +18 -15
  13. package/dist/types/src/components/BaseDatePicker.vue.d.ts +107 -74
  14. package/dist/types/src/components/BaseDraggable.vue.d.ts +35 -20
  15. package/dist/types/src/components/BaseFilePicker.vue.d.ts +43 -42
  16. package/dist/types/src/components/BaseFilePickerCrop.vue.d.ts +43 -40
  17. package/dist/types/src/components/BaseFileUploader.vue.d.ts +83 -62
  18. package/dist/types/src/components/BaseGantt.vue.d.ts +424 -0
  19. package/dist/types/src/components/BaseHeader.vue.d.ts +81 -66
  20. package/dist/types/src/components/BaseIconPicker.vue.d.ts +27 -34
  21. package/dist/types/src/components/BaseLayoutNotificationItemContent.vue.d.ts +18 -15
  22. package/dist/types/src/components/BaseSideNavigation.vue.d.ts +11 -26
  23. package/dist/types/src/components/BaseSideNavigationItem.vue.d.ts +27 -32
  24. package/dist/types/src/components/BaseTabItem.vue.d.ts +27 -32
  25. package/dist/types/src/components/BaseTabs.vue.d.ts +11 -26
  26. package/dist/types/src/services/gantt/format.d.ts +24 -0
  27. package/dist/types/src/services/gantt/timescale.d.ts +26 -0
  28. package/dist/types/src/services/gantt/types.d.ts +67 -0
  29. package/package.json +1 -1
  30. package/src/components/BaseGantt.stories.js +130 -0
  31. package/src/components/BaseGantt.vue +333 -0
  32. package/src/components/BaseNumber.vue +37 -23
  33. package/src/services/gantt/format.ts +113 -0
  34. package/src/services/gantt/timescale.ts +243 -0
  35. package/src/services/gantt/types.ts +75 -0
@@ -0,0 +1,333 @@
1
+ <template>
2
+ <div
3
+ class="flex"
4
+ :style="{
5
+ maxHeight: `${maxHeight}px`,
6
+ }"
7
+ >
8
+ <!-- Sidebar -->
9
+ <div
10
+ class="border-r border-slate-300 relative shrink-0"
11
+ :style="{ minWidth: `${SIDEBAR_WIDTH}px` }"
12
+ >
13
+ <!-- Top-left Corner-->
14
+ <div
15
+ :style="{
16
+ height: `${HEADER_HEIGHT}px`,
17
+ zIndex: 1
18
+ }"
19
+ class="border-b relative border-slate-300 bg-white"
20
+ />
21
+
22
+ <!-- Sidebar Items-->
23
+
24
+ <ul
25
+ class=""
26
+ :style="{
27
+ transform: `translateY(${scrollY}px)`,
28
+ }"
29
+ >
30
+ <li
31
+ v-for="row in rowsInternal"
32
+ :key="row.id"
33
+ class="border-b border-slate-300 flex last:border-none"
34
+ :style="{
35
+ height: `${props.rowHeight}px`,
36
+ }"
37
+ >
38
+ <slot
39
+ name="sidebarItem"
40
+ :row="row"
41
+ >
42
+ <div class="px-2 flex items-center">
43
+ <p class="font-semibold text-sm">
44
+ {{ row.name }}
45
+ </p>
46
+ </div>
47
+ </slot>
48
+ </li>
49
+ </ul>
50
+ </div>
51
+
52
+ <!-- Content -->
53
+
54
+ <div
55
+ ref="contentRef"
56
+ class="grow flex flex-col relative overflow-hidden"
57
+ >
58
+ <!-- Time Scale Header -->
59
+
60
+ <div
61
+ class="bg-white border-b shrink-0 border-slate-300 w-full"
62
+ :style="{
63
+ zIndex: 1,
64
+ height: HEADER_HEIGHT + 'px',
65
+ width: `${width}px`,
66
+ transform: `translateX(${scrollX}px)`,
67
+ }"
68
+ >
69
+ <svg
70
+ :view-box="`${width} ${HEADER_HEIGHT}`"
71
+ :width="width"
72
+ :height="HEADER_HEIGHT"
73
+ >
74
+ <g
75
+ v-for="group in groups"
76
+ :key="group.x"
77
+ >
78
+ <text
79
+ :x="group.labelX"
80
+ :y="15"
81
+ class="text-[12px] font-semibold text-slate-900"
82
+ fill="currentColor"
83
+ :text-anchor="group.labelTextAnchor"
84
+ >
85
+ {{ group.label }}
86
+ </text>
87
+ <line
88
+ :x1="group.x + group.width"
89
+ :x2="group.x + group.width"
90
+ :y1="0"
91
+ :y2="HEADER_HEIGHT"
92
+ :stroke="slate[300]"
93
+ ></line>
94
+ </g>
95
+
96
+ <g
97
+ v-for="tick in ticks"
98
+ :key="tick.x"
99
+ :transform="`translate(${tick.x}, 0)`"
100
+ >
101
+ <text
102
+ :x="tick.align == 'middle' ? tick.width / 2 : 0"
103
+ :y="33"
104
+ class="text-[11px] font-normal text-slate-600"
105
+ fill="currentColor"
106
+ text-anchor="middle"
107
+ >
108
+ {{ tick.label }}
109
+ </text>
110
+
111
+ <line
112
+ v-if="tick.align == 'middle'"
113
+ :x1="tick.width"
114
+ :x2="tick.width"
115
+ :y1="24"
116
+ :y2="HEADER_HEIGHT"
117
+ :stroke="slate[300]"
118
+ ></line>
119
+ </g>
120
+ </svg>
121
+ </div>
122
+
123
+ <div
124
+ v-if="currentGroup"
125
+ class="absolute top-0 left-0 inline-flex"
126
+ :style="{
127
+ zIndex: 1,
128
+ }"
129
+ >
130
+ <div class="text-xs font-semibold pt-[3px] bg-white px-2">
131
+ {{ currentGroup.label }}
132
+ </div>
133
+ <div class="bg-gradient-to-r from-white to-transparent w-20" />
134
+ </div>
135
+
136
+ <!-- Gantt Items -->
137
+
138
+ <ul
139
+ ref="itemsRef"
140
+ class="relative overflow-scroll grow"
141
+ >
142
+ <li
143
+ v-for="row in rowsInternal"
144
+ :key="row.id"
145
+ class="border-b relative border-slate-300 last:border-none"
146
+ :style="{
147
+ height: `${props.rowHeight}px`,
148
+ width: `${width}px`,
149
+ }"
150
+ >
151
+ <button
152
+ v-for="item in row.items"
153
+ :key="item.id"
154
+ type="button"
155
+ class="absolute flex"
156
+ :style="{
157
+ transform: `translate(${item.x}px, ${item.y}px)`,
158
+ height: item.height + 'px',
159
+ width: item.width + 'px',
160
+ }"
161
+ :title="`${item.name} - ${item.start.toFormat('yyyy-MM-dd HH:mm:ss')} - ${item.end.toFormat('yyyy-MM-dd HH:mm:ss')}`"
162
+ @click="$emit('item:click', item)"
163
+ >
164
+ <slot
165
+ name="item"
166
+ :item="item"
167
+ >
168
+ <div
169
+ :style="{
170
+ backgroundColor: item.color,
171
+ }"
172
+ class="flex w-full h-full items-center rounded hover:opacity-80 duration-200"
173
+ >
174
+ <p
175
+ class="text-white text-xs font-medium px-2 py-1 truncate"
176
+ style="text-shadow: 0.5px 0.5px rgba(0,0,0,0.1);"
177
+ >
178
+ {{ item.name }}
179
+ </p>
180
+ </div>
181
+ </slot>
182
+ </button>
183
+ </li>
184
+ </ul>
185
+
186
+ <!-- Vertical lines -->
187
+
188
+ <div
189
+ class="absolute top-0 left-0 pointer-events-none"
190
+ :style="{
191
+ width: `${width}px`,
192
+ height: height + 'px',
193
+ transform: `translateX(${scrollX}px)`,
194
+ }"
195
+ >
196
+ <svg
197
+ :view-box="`${width} ${HEADER_HEIGHT}`"
198
+ :width="width"
199
+ :height="height"
200
+ >
201
+ <g
202
+ v-for="group in groups"
203
+ :key="group.x"
204
+ >
205
+ <line
206
+ :x1="group.x"
207
+ :x2="group.x"
208
+ y1="0"
209
+ :y2="height"
210
+ stroke="black"
211
+ :opacity="0.2"
212
+ ></line>
213
+ </g>
214
+ <g
215
+ v-for="tick in ticks"
216
+ :key="tick.x"
217
+ >
218
+ <line
219
+ :x1="tick.x"
220
+ :x2="tick.x"
221
+ :y1="0"
222
+ :y2="height"
223
+ stroke="black"
224
+ :opacity="0.1"
225
+ ></line>
226
+ </g>
227
+ </svg>
228
+ </div>
229
+ </div>
230
+ </div>
231
+ </template>
232
+
233
+ <script lang="ts" setup>
234
+ import { useElementSize, useScroll } from '@vueuse/core';
235
+ import { Format } from '@/services/gantt/format';
236
+ import { FormatConfig, GanttRow, GanttRowFormatted, Group, Tick } from '@/services/gantt/types';
237
+ import { debounce } from 'lodash';
238
+ import { slate } from 'tailwindcss/colors';
239
+
240
+ const props = withDefaults(defineProps<{
241
+ rows: GanttRow[],
242
+ rowHeight?: number,
243
+ rowPadding?: number,
244
+ maxHeight?: number,
245
+ }>(), {
246
+ rowHeight: 40,
247
+ rowPadding: 4,
248
+ maxHeight: undefined,
249
+ });
250
+
251
+ defineEmits([
252
+ 'item:click',
253
+ ]);
254
+
255
+ // Config
256
+
257
+ const SIDEBAR_WIDTH = 120;
258
+ const HEADER_HEIGHT = 40;
259
+
260
+ // Init
261
+
262
+ const contentRef = ref<HTMLDivElement | null>(null);
263
+ const contentSize = useElementSize(contentRef);
264
+
265
+ const width = ref(800);
266
+ const height = ref(100);
267
+
268
+ const rowsInternal = ref<GanttRowFormatted[]>([]);
269
+ const groups = ref<Group[]>([]);
270
+ const ticks = ref<Tick[]>([]);
271
+
272
+ const config = computed<FormatConfig>(() => {
273
+ return {
274
+ rows: props.rows,
275
+ minWidth: contentSize.width.value,
276
+ rowHeight: props.rowHeight,
277
+ rowPadding: props.rowPadding,
278
+ };
279
+ });
280
+
281
+ function init() {
282
+
283
+ const format = (new Format(config.value)).handle();
284
+
285
+ width.value = format.width;
286
+ height.value = format.height;
287
+
288
+ rowsInternal.value = format.rows;
289
+
290
+ groups.value = format.groups;
291
+ ticks.value = format.ticks;
292
+ }
293
+
294
+ // Init triggers
295
+
296
+ const initDebounced = debounce(init, 200);
297
+
298
+ watch(() => config.value, initDebounced, { deep: true });
299
+
300
+ // Scroll
301
+
302
+ const itemsRef = ref<HTMLUListElement | null>(null);
303
+
304
+ const scrollX = ref(0);
305
+ const scrollY = ref(0);
306
+
307
+ useScroll(itemsRef, {
308
+ onScroll: (e: any) => {
309
+ if (!e.target) return;
310
+
311
+ scrollX.value = -e.target.scrollLeft;
312
+ scrollY.value = -e.target.scrollTop;
313
+ },
314
+ });
315
+
316
+ // Current Group
317
+
318
+ const currentGroup = computed<Group | undefined>(() => {
319
+
320
+ const offsetLeft = -scrollX.value;
321
+
322
+ return groups.value
323
+ // Sort by x descending
324
+ .sort((a, b) => {
325
+ return b.x - a.x;
326
+ })
327
+ .find((group) => {
328
+ // - 1 to avoid flashing when scrolling
329
+ return group.x <= offsetLeft && group.x + group.width >= offsetLeft - 1;
330
+ });
331
+ });
332
+
333
+ </script>
@@ -9,7 +9,7 @@
9
9
  leave-to-class="transform scale-90 opacity-0"
10
10
  >
11
11
  <div
12
- v-if="showInvalidInput"
12
+ v-if="showInvalidInput && invalidInput"
13
13
  class="absolute left-0 top-full z-[1]"
14
14
  >
15
15
  <div
@@ -171,34 +171,50 @@ function convertToNumber(
171
171
  value: string | number | null | undefined
172
172
  ): number | null {
173
173
  if (value === null) return null;
174
- let number = parseFloat(value + '');
174
+ const number = parseFloat(value + '');
175
175
  if (Number.isNaN(number)) {
176
176
  return null;
177
177
  }
178
+ return number;
179
+ }
180
+
181
+ function convertToValidNumber(
182
+ value: string | number | null | undefined
183
+ ): number | null {
184
+ let number = convertToNumber(value);
185
+
186
+ if (number === null) return null;
187
+
178
188
  if (hasMax.value) {
179
189
  number = Math.min(number, props.max as number);
180
190
  }
191
+
181
192
  if (hasMin.value) {
182
193
  number = Math.max(number, props.min as number);
183
194
  }
195
+
184
196
  return round(number, precision.value);
185
197
  }
186
198
 
187
199
  const valueInternal = ref<null | string | number>(null);
188
200
 
189
- const realValueInternal = computed<number | null>(() => {
201
+ const valueInternalNumber = computed<number | null>(() => {
190
202
  return convertToNumber(valueInternal.value);
191
203
  });
192
204
 
205
+ const valueInternalValidNumber = computed<number | null>(() => {
206
+ return convertToValidNumber(valueInternal.value);
207
+ });
208
+
193
209
  const realValueInternalAsString = computed<string>(() => {
194
- if (realValueInternal.value === null) return '';
195
- return realValueInternal.value.toLocaleString('fullwide', {
210
+ if (valueInternalValidNumber.value === null) return '';
211
+ return valueInternalValidNumber.value.toLocaleString('fullwide', {
196
212
  useGrouping: false,
197
213
  });
198
214
  });
199
215
 
200
216
  const invalidInput = computed(() => {
201
- if (realValueInternal.value == null && valueInternal.value == '') {
217
+ if (valueInternalValidNumber.value == null && valueInternal.value == '') {
202
218
  return false;
203
219
  }
204
220
 
@@ -206,13 +222,13 @@ const invalidInput = computed(() => {
206
222
  });
207
223
 
208
224
  const tooBig = computed(() => {
209
- if (realValueInternal.value === null) return false;
210
- return hasMax.value && realValueInternal.value > (props.max as number);
225
+ if (valueInternalNumber.value === null) return false;
226
+ return hasMax.value && valueInternalNumber.value > (props.max as number);
211
227
  });
212
228
 
213
229
  const tooSmall = computed(() => {
214
- if (realValueInternal.value === null) return false;
215
- return hasMin.value && realValueInternal.value < (props.min as number);
230
+ if (valueInternalNumber.value === null) return false;
231
+ return hasMin.value && valueInternalNumber.value < (props.min as number);
216
232
  });
217
233
 
218
234
  const tooPrecise = computed(() => {
@@ -224,10 +240,8 @@ const tooPrecise = computed(() => {
224
240
  return parts[1].length > precision.value;
225
241
  });
226
242
 
227
- valueInternal.value = convertToNumber(props.modelValue);
228
-
229
- if (valueInternal.value != props.modelValue) {
230
- emitUpdate(realValueInternal.value);
243
+ if (convertToValidNumber(props.modelValue) != props.modelValue) {
244
+ emitUpdate(valueInternalValidNumber.value);
231
245
  }
232
246
 
233
247
  let timeoutId = undefined as undefined | number;
@@ -247,7 +261,7 @@ function onInput(event: any) {
247
261
 
248
262
  valueInternal.value = value;
249
263
 
250
- emitUpdate(realValueInternal.value);
264
+ emitUpdate(valueInternalValidNumber.value);
251
265
 
252
266
  nextTick(() => {
253
267
  showHideInvalidOnInput();
@@ -306,46 +320,46 @@ function updateInternalValueToRealValue() {
306
320
  if (!props.autoFix) {
307
321
  return;
308
322
  }
309
- if (realValueInternal.value === null) {
323
+ if (valueInternalValidNumber.value === null) {
310
324
  valueInternal.value = '';
311
325
  return;
312
326
  }
313
327
  valueInternal.value = round(
314
- realValueInternal.value ?? defaultValue.value,
328
+ valueInternalValidNumber.value ?? defaultValue.value,
315
329
  precision.value
316
330
  );
317
331
  }
318
332
 
319
333
  function increment() {
320
334
  if (props.disabled) return;
321
- if (realValueInternal.value === null) {
335
+ if (valueInternalValidNumber.value === null) {
322
336
  valueInternal.value = defaultValue.value;
323
337
  } else {
324
338
  const newValue = round(
325
- realValueInternal.value + stepNormalized.value,
339
+ valueInternalValidNumber.value + stepNormalized.value,
326
340
  precision.value
327
341
  );
328
342
  if (!hasMax.value || newValue <= (props.max as number)) {
329
343
  valueInternal.value = newValue;
330
344
  }
331
345
  }
332
- emitUpdate(realValueInternal.value);
346
+ emitUpdate(valueInternalValidNumber.value);
333
347
  }
334
348
 
335
349
  function decrement() {
336
350
  if (props.disabled) return;
337
- if (realValueInternal.value === null) {
351
+ if (valueInternalValidNumber.value === null) {
338
352
  valueInternal.value = defaultValue.value;
339
353
  } else {
340
354
  const newValue = round(
341
- realValueInternal.value - stepNormalized.value,
355
+ valueInternalValidNumber.value - stepNormalized.value,
342
356
  precision.value
343
357
  );
344
358
  if (!hasMin.value || newValue >= (props.min as number)) {
345
359
  valueInternal.value = newValue;
346
360
  }
347
361
  }
348
- emitUpdate(realValueInternal.value);
362
+ emitUpdate(valueInternalValidNumber.value);
349
363
  }
350
364
 
351
365
  const borderColor = computed(() => {
@@ -0,0 +1,113 @@
1
+ import { DateTime } from "luxon";
2
+ import { FormatConfig, GanttItem, GanttItemFormatted, GanttRow, GanttRowFormatted } from "./types";
3
+ import { minBy } from "lodash";
4
+ import { Timescale } from "./timescale";
5
+
6
+ export class Format {
7
+
8
+ private rows: GanttRow[];
9
+ private config: FormatConfig;
10
+
11
+ constructor(config: FormatConfig) {
12
+ this.rows = config.rows;
13
+ this.config = config;
14
+ }
15
+
16
+ public handle() {
17
+
18
+ // Format rows
19
+ const rowsFormatted = this.formatGanttRows();
20
+
21
+ // Get min and max
22
+ let { min, max } = this.getMinMax(rowsFormatted);
23
+
24
+ // Get timescale
25
+ const timescale = (new Timescale(this.config.minWidth, min, max)).handle();
26
+
27
+ // Padded min and max
28
+ min = timescale.min;
29
+ max = timescale.max;
30
+
31
+ const millisecondToPixel = timescale.millisecondToPixel;
32
+
33
+ // Set x, y, width and height
34
+
35
+ rowsFormatted.forEach((row) => {
36
+ row.items.forEach((item) => {
37
+
38
+ const x = (item.start.toMillis() - min.toMillis()) * millisecondToPixel;
39
+ const y = this.config.rowPadding;
40
+ const width = item.milliseconds * millisecondToPixel;
41
+ const height = this.config.rowHeight - (this.config.rowPadding * 2);
42
+
43
+ item.x = x;
44
+ item.y = y;
45
+ item.width = width;
46
+ item.height = height;
47
+ });
48
+ });
49
+
50
+ return {
51
+ rows: rowsFormatted,
52
+ min,
53
+ max,
54
+ millisecondToPixel,
55
+ height: this.config.rowHeight * this.rows.length,
56
+ width: timescale.width,
57
+ groups: timescale.groups,
58
+ ticks: timescale.ticks,
59
+ };
60
+ }
61
+
62
+ formatGanttRows(): GanttRowFormatted[] {
63
+ return this.rows.map((row) => {
64
+ return this.formatGanttRow(row);
65
+ }) as GanttRowFormatted[];
66
+ }
67
+
68
+ formatGanttRow(row: GanttRow): GanttRowFormatted {
69
+ const itemsFormatted = row.items.map((item) => {
70
+ return this.formatGanttItem(item);
71
+ });
72
+
73
+ return {
74
+ id: row.id,
75
+ name: row.name,
76
+ meta: row.meta,
77
+ items: itemsFormatted,
78
+ };
79
+ }
80
+
81
+ formatGanttItem(item: GanttItem): GanttItemFormatted {
82
+ const start = DateTime.fromISO(item.start);
83
+ const end = DateTime.fromISO(item.end);
84
+ const milliseconds = end.diff(start, "milliseconds").milliseconds;
85
+
86
+ return {
87
+ id: item.id,
88
+ name: item.name,
89
+ meta: item.meta,
90
+ color: item.color,
91
+ start,
92
+ end,
93
+ milliseconds,
94
+ x: 0,
95
+ y: 0,
96
+ width: 0,
97
+ height: 0,
98
+ } as GanttItemFormatted;
99
+ }
100
+
101
+ getMinMax(rowsFormatted: GanttRowFormatted[]) {
102
+ const flatItems = rowsFormatted.flatMap((row) => row.items);
103
+ const flatDateTimes = flatItems.flatMap((item) => [item.start, item.end]);
104
+
105
+ const min = minBy(flatDateTimes, (dateTime) => dateTime.toMillis())?.toMillis() || 0;
106
+ const max = Math.max(...flatDateTimes.map((dateTime) => dateTime.toMillis()));
107
+
108
+ const minDateTime = DateTime.fromMillis(min);
109
+ const maxDateTime = DateTime.fromMillis(max);
110
+
111
+ return { min: minDateTime, max: maxDateTime };
112
+ }
113
+ }