@pgcorp/ui-kit 0.8.0 → 0.9.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.
- package/README.md +174 -0
- package/docs/public-api.md +8 -0
- package/docs/theming.md +17 -0
- package/package.json +10 -5
- package/src/components/layout/SResizeHandle.vue +45 -5
- package/src/components/shared/_internal/SDialogSurface.css +18 -0
- package/src/components/shared/_internal/SDialogSurface.vue +6 -1
- package/src/components/shared/_internal/dialogSurface.ts +7 -1
- package/src/components/shared/containers/SDrawer.css +11 -2
- package/src/components/shared/containers/SDrawer.vue +43 -7
- package/src/components/shared/controls/SInteractiveSurface.css +50 -4
- package/src/components/shared/controls/SInteractiveSurface.vue +44 -30
- package/src/components/shared/controls/STextarea.ts +13 -0
- package/src/components/shared/controls/STextarea.vue +391 -37
- package/src/components/shared/data-display/SActionCard.css +6 -2
- package/src/components/shared/data-display/SActionCard.vue +13 -3
- package/src/components/shared/data-display/SActionListItem.css +7 -8
- package/src/components/shared/data-display/SActionListItem.vue +21 -18
- package/src/components/shared/data-display/SChart.vue +2 -2
- package/src/components/shared/data-display/SChip.css +7 -2
- package/src/components/shared/data-display/SCodeBlock.vue +4 -2
- package/src/components/shared/data-display/SVirtualList.ts +33 -0
- package/src/components/shared/data-display/SVirtualList.vue +321 -36
- package/src/internal/chartGeometry.ts +126 -22
- package/src/styles/tokens.css +3 -0
|
@@ -79,6 +79,7 @@ export interface ChartLegendItem {
|
|
|
79
79
|
export interface ChartTick {
|
|
80
80
|
readonly value: number
|
|
81
81
|
readonly label: string
|
|
82
|
+
readonly displayLabel: string
|
|
82
83
|
readonly position: number
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -87,6 +88,7 @@ export interface ChartCategoryLabel {
|
|
|
87
88
|
readonly label: string
|
|
88
89
|
readonly lines: readonly string[]
|
|
89
90
|
readonly x: number
|
|
91
|
+
readonly textAnchor: 'start' | 'middle' | 'end'
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
export interface ChartGridLine {
|
|
@@ -158,6 +160,12 @@ interface PlotRect {
|
|
|
158
160
|
readonly height: number
|
|
159
161
|
}
|
|
160
162
|
|
|
163
|
+
interface ResolvedDomain {
|
|
164
|
+
readonly minimum: number
|
|
165
|
+
readonly maximum: number
|
|
166
|
+
readonly ticks: readonly number[]
|
|
167
|
+
}
|
|
168
|
+
|
|
161
169
|
const SIZE_DIMENSIONS: Readonly<Record<SChartSize, ChartDimensions>> = {
|
|
162
170
|
compact: { width: 360, height: 96 },
|
|
163
171
|
sm: { width: 640, height: 240 },
|
|
@@ -393,6 +401,7 @@ function resolvePlot(
|
|
|
393
401
|
xAxis: boolean,
|
|
394
402
|
yAxis: boolean,
|
|
395
403
|
dataLabels: SChartDataLabels,
|
|
404
|
+
yTickLabels: readonly string[],
|
|
396
405
|
): PlotRect {
|
|
397
406
|
if (type === 'pie' || type === 'donut') {
|
|
398
407
|
const inset = size === 'compact' ? 4 : 12
|
|
@@ -403,7 +412,15 @@ function resolvePlot(
|
|
|
403
412
|
: (size === 'compact' ? 14 : 24)
|
|
404
413
|
const right = size === 'compact' ? 6 : 16
|
|
405
414
|
const bottom = xAxis ? (size === 'compact' ? 32 : 48) : (size === 'compact' ? 5 : 12)
|
|
406
|
-
const
|
|
415
|
+
const defaultLeft = yAxis ? (size === 'compact' ? 34 : 54) : (size === 'compact' ? 6 : 14)
|
|
416
|
+
const tickFontSize = size === 'compact' ? 9 : 11
|
|
417
|
+
const tickWidth = yAxis
|
|
418
|
+
? Math.max(0, ...yTickLabels.map((label) => estimateAxisTextWidth(label, tickFontSize)))
|
|
419
|
+
: 0
|
|
420
|
+
const desiredLeft = yAxis ? Math.max(defaultLeft, Math.ceil(tickWidth + 16)) : defaultLeft
|
|
421
|
+
const minimumPlotWidth = Math.min(size === 'compact' ? 72 : 120, dimensions.width * 0.4)
|
|
422
|
+
const maximumLeft = Math.max(0, dimensions.width - right - minimumPlotWidth)
|
|
423
|
+
const left = Math.min(desiredLeft, maximumLeft)
|
|
407
424
|
return {
|
|
408
425
|
x: left,
|
|
409
426
|
y: top,
|
|
@@ -412,6 +429,28 @@ function resolvePlot(
|
|
|
412
429
|
}
|
|
413
430
|
}
|
|
414
431
|
|
|
432
|
+
function estimateAxisTextWidth(label: string, fontSize: number): number {
|
|
433
|
+
const units = Array.from(label).reduce((total, character) => {
|
|
434
|
+
if (/\s/u.test(character)) return total + 0.36
|
|
435
|
+
if (/[.,:;!|ilI1'`]/u.test(character)) return total + 0.34
|
|
436
|
+
if (/[MW@%ШЩЖЮ]/u.test(character)) return total + 0.92
|
|
437
|
+
return total + 0.62
|
|
438
|
+
}, 0)
|
|
439
|
+
return units * fontSize
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function truncateAxisLabelToWidth(label: string, availableWidth: number, fontSize: number): string {
|
|
443
|
+
if (estimateAxisTextWidth(label, fontSize) <= availableWidth) return label
|
|
444
|
+
const ellipsis = '…'
|
|
445
|
+
const characters = Array.from(label)
|
|
446
|
+
while (characters.length > 0) {
|
|
447
|
+
const candidate = `${characters.join('')}${ellipsis}`
|
|
448
|
+
if (estimateAxisTextWidth(candidate, fontSize) <= availableWidth) return candidate
|
|
449
|
+
characters.pop()
|
|
450
|
+
}
|
|
451
|
+
return ellipsis
|
|
452
|
+
}
|
|
453
|
+
|
|
415
454
|
function truncateAxisWord(word: string, maximumCharacters: number): string {
|
|
416
455
|
if (word.length <= maximumCharacters) return word
|
|
417
456
|
return `${word.slice(0, Math.max(1, maximumCharacters - 1))}…`
|
|
@@ -461,16 +500,9 @@ function cartesianModel(
|
|
|
461
500
|
series: readonly SChartSeries[],
|
|
462
501
|
dimensions: ChartDimensions,
|
|
463
502
|
plot: PlotRect,
|
|
503
|
+
resolvedDomain: ResolvedDomain,
|
|
504
|
+
yTickLabels: readonly string[],
|
|
464
505
|
): Omit<ChartModel, 'empty' | 'emptyReason' | 'slices' | 'centerX' | 'centerY'> {
|
|
465
|
-
const domainValues = input.type === 'bar' && input.barMode === 'stacked'
|
|
466
|
-
? resolveStackedDomainValues(series, categories.length)
|
|
467
|
-
: series.flatMap((item) => [...item.values])
|
|
468
|
-
const resolvedDomain = resolveDomain(
|
|
469
|
-
input.domain,
|
|
470
|
-
domainValues,
|
|
471
|
-
input.type === 'bar' || input.type === 'area',
|
|
472
|
-
input.yTickCount,
|
|
473
|
-
)
|
|
474
506
|
const range = resolvedDomain.maximum - resolvedDomain.minimum
|
|
475
507
|
const yAt = (value: number): number => plot.y + plot.height - ((value - resolvedDomain.minimum) / range) * plot.height
|
|
476
508
|
const baselineY = Math.max(plot.y, Math.min(plot.y + plot.height, yAt(0)))
|
|
@@ -552,17 +584,57 @@ function cartesianModel(
|
|
|
552
584
|
if (input.xLabels === 'endpoints') return index === 0 || index === categories.length - 1
|
|
553
585
|
return categories.length <= 6 || index === 0 || index === categories.length - 1
|
|
554
586
|
}
|
|
555
|
-
const
|
|
556
|
-
|
|
587
|
+
const visibleCategories = categories
|
|
588
|
+
.map((category, index) => ({ category, index, x: xAt(index) }))
|
|
589
|
+
.filter(({ index }) => showXLabel(index))
|
|
590
|
+
const xLabels = visibleCategories.map(({ category, x }, visibleIndex): ChartCategoryLabel => {
|
|
591
|
+
const previous = visibleCategories[visibleIndex - 1]
|
|
592
|
+
const next = visibleCategories[visibleIndex + 1]
|
|
593
|
+
if (visibleCategories.length === 1) {
|
|
594
|
+
return {
|
|
595
|
+
key: category.key,
|
|
596
|
+
label: category.label,
|
|
597
|
+
lines: wrapAxisLabel(category.label, plot.width),
|
|
598
|
+
x: plot.x + plot.width / 2,
|
|
599
|
+
textAnchor: 'middle',
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (!previous) {
|
|
603
|
+
const rightBoundary = (x + next!.x) / 2
|
|
604
|
+
return {
|
|
605
|
+
key: category.key,
|
|
606
|
+
label: category.label,
|
|
607
|
+
lines: wrapAxisLabel(category.label, rightBoundary - plot.x),
|
|
608
|
+
x: plot.x,
|
|
609
|
+
textAnchor: 'start',
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (!next) {
|
|
613
|
+
const leftBoundary = (previous.x + x) / 2
|
|
614
|
+
return {
|
|
557
615
|
key: category.key,
|
|
558
616
|
label: category.label,
|
|
559
|
-
lines: wrapAxisLabel(category.label,
|
|
560
|
-
x:
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
617
|
+
lines: wrapAxisLabel(category.label, plot.x + plot.width - leftBoundary),
|
|
618
|
+
x: plot.x + plot.width,
|
|
619
|
+
textAnchor: 'end',
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
const leftBoundary = (previous.x + x) / 2
|
|
623
|
+
const rightBoundary = (x + next.x) / 2
|
|
624
|
+
return {
|
|
625
|
+
key: category.key,
|
|
626
|
+
label: category.label,
|
|
627
|
+
lines: wrapAxisLabel(category.label, 2 * Math.min(x - leftBoundary, rightBoundary - x)),
|
|
628
|
+
x,
|
|
629
|
+
textAnchor: 'middle',
|
|
630
|
+
}
|
|
631
|
+
})
|
|
632
|
+
const tickFontSize = input.size === 'compact' ? 9 : 11
|
|
633
|
+
const tickLabelWidth = Math.max(1, plot.x - 16)
|
|
634
|
+
const yTicks = resolvedDomain.ticks.map((value, index) => ({
|
|
564
635
|
value,
|
|
565
|
-
label:
|
|
636
|
+
label: yTickLabels[index]!,
|
|
637
|
+
displayLabel: truncateAxisLabelToWidth(yTickLabels[index]!, tickLabelWidth, tickFontSize),
|
|
566
638
|
position: yAt(value),
|
|
567
639
|
}))
|
|
568
640
|
const horizontalGrid = yTicks.map((tick) => ({
|
|
@@ -725,6 +797,32 @@ export function resolveChartModel(input: ResolveChartModelInput): ChartModel {
|
|
|
725
797
|
centerY: dimensions.height / 2,
|
|
726
798
|
}
|
|
727
799
|
}
|
|
800
|
+
if (input.type === 'pie' || input.type === 'donut') {
|
|
801
|
+
const plot = resolvePlot(
|
|
802
|
+
dimensions,
|
|
803
|
+
input.type,
|
|
804
|
+
input.size,
|
|
805
|
+
input.xAxis,
|
|
806
|
+
input.yAxis,
|
|
807
|
+
input.dataLabels,
|
|
808
|
+
[],
|
|
809
|
+
)
|
|
810
|
+
return radialModel(input, categories, series, dimensions, plot)
|
|
811
|
+
}
|
|
812
|
+
const domainValues = input.type === 'bar' && input.barMode === 'stacked'
|
|
813
|
+
? resolveStackedDomainValues(series, categories.length)
|
|
814
|
+
: series.flatMap((item) => [...item.values])
|
|
815
|
+
const resolvedDomain = resolveDomain(
|
|
816
|
+
input.domain,
|
|
817
|
+
domainValues,
|
|
818
|
+
input.type === 'bar' || input.type === 'area',
|
|
819
|
+
input.yTickCount,
|
|
820
|
+
)
|
|
821
|
+
const yTickLabels = resolvedDomain.ticks.map((value) => formatValue(
|
|
822
|
+
input.valueFormatter,
|
|
823
|
+
value,
|
|
824
|
+
`yTick.${value}`,
|
|
825
|
+
))
|
|
728
826
|
const plot = resolvePlot(
|
|
729
827
|
dimensions,
|
|
730
828
|
input.type,
|
|
@@ -732,11 +830,17 @@ export function resolveChartModel(input: ResolveChartModelInput): ChartModel {
|
|
|
732
830
|
input.xAxis,
|
|
733
831
|
input.yAxis,
|
|
734
832
|
input.dataLabels,
|
|
833
|
+
yTickLabels,
|
|
834
|
+
)
|
|
835
|
+
const model = cartesianModel(
|
|
836
|
+
input,
|
|
837
|
+
categories,
|
|
838
|
+
series,
|
|
839
|
+
dimensions,
|
|
840
|
+
plot,
|
|
841
|
+
resolvedDomain,
|
|
842
|
+
yTickLabels,
|
|
735
843
|
)
|
|
736
|
-
if (input.type === 'pie' || input.type === 'donut') {
|
|
737
|
-
return radialModel(input, categories, series, dimensions, plot)
|
|
738
|
-
}
|
|
739
|
-
const model = cartesianModel(input, categories, series, dimensions, plot)
|
|
740
844
|
return {
|
|
741
845
|
...model,
|
|
742
846
|
empty: false,
|
package/src/styles/tokens.css
CHANGED
|
@@ -116,6 +116,9 @@
|
|
|
116
116
|
--s-control-hitbox-md: 2.5rem;
|
|
117
117
|
--s-control-hitbox-lg: 3rem;
|
|
118
118
|
--s-control-indicator-size: 1.25rem;
|
|
119
|
+
--s-resize-handle-hitbox-compact: 0.375rem;
|
|
120
|
+
--s-resize-handle-hitbox-standard: 0.75rem;
|
|
121
|
+
--s-resize-handle-hitbox-spacious: 2rem;
|
|
119
122
|
--s-icon-size-2xs: 0.75rem;
|
|
120
123
|
--s-icon-size-xs: 1rem;
|
|
121
124
|
--s-icon-size-sm: 1.25rem;
|