@startsimpli/ui 0.4.139 → 0.4.140
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.140",
|
|
4
4
|
"description": "Shared UI components package for StartSimpli applications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -126,8 +126,8 @@
|
|
|
126
126
|
"tailwindcss": "^3.4.19",
|
|
127
127
|
"tsup": "^8.5.1",
|
|
128
128
|
"typescript": "^6.0.3",
|
|
129
|
-
"@startsimpli/
|
|
130
|
-
"@startsimpli/
|
|
129
|
+
"@startsimpli/i18n": "0.1.3",
|
|
130
|
+
"@startsimpli/realtime": "0.0.2"
|
|
131
131
|
},
|
|
132
132
|
"keywords": [
|
|
133
133
|
"ui",
|
|
@@ -53,25 +53,32 @@ export interface TimeSeriesChartProps {
|
|
|
53
53
|
/**
|
|
54
54
|
* Series colours, in order, from the theme contract (theme/contract.css) — so
|
|
55
55
|
* they follow the app's brand and flip with `.dark` like every other surface.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
56
|
+
*
|
|
57
|
+
* Categorical only: a series' colour says which series it is, nothing more.
|
|
58
|
+
* `--success` and `--destructive` are deliberately absent — they mean good/bad
|
|
59
|
+
* (gain/loss), and a series that happened to land on one would assert that.
|
|
60
|
+
* `--secondary` and `--accent` are surface tones, too faint to read as a
|
|
61
|
+
* stroke. That leaves three tokens, so distinguishability comes mostly from
|
|
62
|
+
* the dash cycle below.
|
|
58
63
|
*/
|
|
59
64
|
export const SERIES_COLORS = [
|
|
60
65
|
'hsl(var(--primary))',
|
|
61
66
|
'hsl(var(--muted-foreground))',
|
|
62
|
-
'hsl(var(--success, 142.1 76.2% 36.3%))',
|
|
63
|
-
'hsl(var(--destructive))',
|
|
64
67
|
'hsl(var(--foreground))',
|
|
65
68
|
] as const
|
|
66
69
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Each pass through the palette gets the next dash: solid, dashed, dotted,
|
|
72
|
+
* dash-dot. 3 colours x 4 dashes = 12 series before a style repeats. The
|
|
73
|
+
* patterns fit the 16px legend swatch so each one shows its full rhythm.
|
|
74
|
+
*/
|
|
75
|
+
export const SERIES_DASHES = [undefined, '6 4', '2 3', '8 3 2 3'] as const
|
|
69
76
|
|
|
70
77
|
export function seriesStroke(index: number): { color: string; dash: string | undefined } {
|
|
71
78
|
const n = SERIES_COLORS.length
|
|
72
79
|
return {
|
|
73
80
|
color: SERIES_COLORS[index % n],
|
|
74
|
-
dash:
|
|
81
|
+
dash: SERIES_DASHES[Math.floor(index / n) % SERIES_DASHES.length],
|
|
75
82
|
}
|
|
76
83
|
}
|
|
77
84
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react'
|
|
2
|
-
import { TimeSeriesChart, SERIES_COLORS, seriesStroke } from '../TimeSeriesChart'
|
|
2
|
+
import { TimeSeriesChart, SERIES_COLORS, SERIES_DASHES, seriesStroke } from '../TimeSeriesChart'
|
|
3
3
|
import type { TimeSeries } from '../lib/series'
|
|
4
4
|
|
|
5
5
|
// jsdom lays nothing out: recharts' ResponsiveContainer renders no chart at a
|
|
@@ -74,6 +74,19 @@ describe('TimeSeriesChart', () => {
|
|
|
74
74
|
expect(new Set(strokes)).toEqual(new Set([SERIES_COLORS[0], SERIES_COLORS[1]]))
|
|
75
75
|
})
|
|
76
76
|
|
|
77
|
+
it('dashes the series that repeat a colour, on the line and in the legend', () => {
|
|
78
|
+
const four = [0, 1, 2, 3].map((k) => ({ ...holding, name: `S${k}` }))
|
|
79
|
+
const { container } = render(<TimeSeriesChart series={four} unit="$" />)
|
|
80
|
+
const lines = linePaths(container)
|
|
81
|
+
expect(lines).toHaveLength(4)
|
|
82
|
+
expect(lines[3].getAttribute('stroke')).toBe(lines[0].getAttribute('stroke'))
|
|
83
|
+
expect(lines[3].getAttribute('stroke-dasharray')).toBe('6 4')
|
|
84
|
+
expect(lines[0].getAttribute('stroke-dasharray')).toBeNull()
|
|
85
|
+
|
|
86
|
+
const swatches = Array.from(container.querySelectorAll('ul svg line'))
|
|
87
|
+
expect(swatches.map((l) => l.getAttribute('stroke-dasharray'))).toEqual([null, null, null, '6 4'])
|
|
88
|
+
})
|
|
89
|
+
|
|
77
90
|
it('shows a legend for two or more series and none for one', () => {
|
|
78
91
|
const { rerender } = render(<TimeSeriesChart series={[strategy, holding]} unit="$" />)
|
|
79
92
|
expect(screen.getByText('Strategy')).toBeInTheDocument()
|
|
@@ -132,10 +145,42 @@ describe('TimeSeriesChart', () => {
|
|
|
132
145
|
})
|
|
133
146
|
|
|
134
147
|
describe('seriesStroke', () => {
|
|
135
|
-
it('
|
|
148
|
+
it('uses only non-semantic tokens, in order', () => {
|
|
149
|
+
expect([...SERIES_COLORS]).toEqual([
|
|
150
|
+
'hsl(var(--primary))',
|
|
151
|
+
'hsl(var(--muted-foreground))',
|
|
152
|
+
'hsl(var(--foreground))',
|
|
153
|
+
])
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('never colours a series as gain or loss', () => {
|
|
157
|
+
// --success / --destructive mean good / bad; a categorical series must not
|
|
158
|
+
// inherit that meaning by landing on one.
|
|
159
|
+
const styles = Array.from({ length: 40 }, (_, i) => seriesStroke(i).color)
|
|
160
|
+
for (const color of styles) {
|
|
161
|
+
expect(color).not.toMatch(/--(success|destructive)\b/)
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('cycles the token palette and steps the dash on each pass', () => {
|
|
136
166
|
const n = SERIES_COLORS.length
|
|
137
167
|
expect(seriesStroke(0)).toEqual({ color: SERIES_COLORS[0], dash: undefined })
|
|
168
|
+
expect(seriesStroke(n - 1)).toEqual({ color: SERIES_COLORS[n - 1], dash: undefined })
|
|
138
169
|
expect(seriesStroke(n)).toEqual({ color: SERIES_COLORS[0], dash: '6 4' })
|
|
139
170
|
expect(seriesStroke(n + 1).color).toBe(SERIES_COLORS[1])
|
|
171
|
+
expect(seriesStroke(2 * n).dash).toBe('2 3')
|
|
172
|
+
expect(seriesStroke(3 * n).dash).toBe('8 3 2 3')
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('keeps every series distinct until colours x dashes run out', () => {
|
|
176
|
+
const distinct = SERIES_COLORS.length * SERIES_DASHES.length
|
|
177
|
+
expect(distinct).toBe(12)
|
|
178
|
+
const keys = Array.from({ length: distinct }, (_, i) => {
|
|
179
|
+
const { color, dash } = seriesStroke(i)
|
|
180
|
+
return `${color}|${dash ?? 'solid'}`
|
|
181
|
+
})
|
|
182
|
+
expect(new Set(keys).size).toBe(distinct)
|
|
183
|
+
// Then the cycle starts over.
|
|
184
|
+
expect(seriesStroke(distinct)).toEqual(seriesStroke(0))
|
|
140
185
|
})
|
|
141
186
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Charts live behind their own subpath (`@startsimpli/ui/charts`) so recharts
|
|
2
2
|
// is only bundled by apps that draw one.
|
|
3
|
-
export { TimeSeriesChart, SERIES_COLORS, seriesStroke } from './TimeSeriesChart'
|
|
3
|
+
export { TimeSeriesChart, SERIES_COLORS, SERIES_DASHES, seriesStroke } from './TimeSeriesChart'
|
|
4
4
|
export type { TimeSeriesChartProps, TimeSeriesReferenceLine } from './TimeSeriesChart'
|
|
5
5
|
export type {
|
|
6
6
|
TimeSeries,
|