@lolmath/ui 9.4.0 → 9.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lolmath/ui",
3
- "version": "9.4.0",
3
+ "version": "9.6.0",
4
4
  "private": false,
5
5
  "description": "lolmath.net UI",
6
6
  "type": "module",
@@ -31,11 +31,14 @@
31
31
  "url": "https://github.com/lolmath/lolmath/issues"
32
32
  },
33
33
  "devDependencies": {
34
+ "@tanstack/charts": "^0.14.0",
34
35
  "@tsdown/css": "^0.22.14",
36
+ "@types/d3-shape": "^3.1.8",
35
37
  "@types/node": "^26.1.1",
36
38
  "@types/postcss-url": "^10.0.4",
37
39
  "@types/react": "^19.2.17",
38
40
  "@types/react-dom": "^19.2.3",
41
+ "d3-shape": "^3.2.0",
39
42
  "lightningcss": "^1.33.0",
40
43
  "postcss-url": "^10.1.4",
41
44
  "publint": "^0.3.22",
@@ -49,11 +52,15 @@
49
52
  "peerDependencies": {
50
53
  "react": ">=19",
51
54
  "react-dom": ">=19",
52
- "tailwindcss": ">=3"
55
+ "tailwindcss": ">=3",
56
+ "@tanstack/charts": ">=0.14.0"
53
57
  },
54
58
  "peerDependenciesMeta": {
55
59
  "tailwindcss": {
56
60
  "optional": true
61
+ },
62
+ "@tanstack/charts": {
63
+ "optional": true
57
64
  }
58
65
  },
59
66
  "exports": {
@@ -63,6 +70,12 @@
63
70
  "default": "./dist/index.mjs"
64
71
  }
65
72
  },
73
+ "./charts": {
74
+ "import": {
75
+ "types": "./dist/charts.d.mts",
76
+ "default": "./dist/charts.mjs"
77
+ }
78
+ },
66
79
  "./css": {
67
80
  "types": "./dist/css.d.ts",
68
81
  "default": "./dist/index.css"
@@ -86,6 +99,7 @@
86
99
  "dev": "tsdown --watch",
87
100
  "publint": "publint",
88
101
  "typecheck": "tsc --noEmit",
89
- "docs": "node ./docs.ts"
102
+ "docs": "node ./docs.ts",
103
+ "test": "vitest run"
90
104
  }
91
105
  }
package/readme.md CHANGED
@@ -70,6 +70,162 @@ fonts.
70
70
  @import "@lolmath/ui/tailwind";
71
71
  ```
72
72
 
73
+ ## Charts
74
+
75
+ `@lolmath/ui/charts` draws [TanStack Charts](https://tanstack.com/charts) in the
76
+ same visual language as the rest of the library.
77
+
78
+ It is a separate entrypoint because `@tanstack/charts` is an **optional peer
79
+ dependency**: install it only if you import from `/charts`, and nothing changes
80
+ for anyone who does not.
81
+
82
+ ```bash
83
+ npm install @tanstack/charts
84
+ ```
85
+
86
+ ```tsx
87
+ import { LineChart } from "@lolmath/ui/charts";
88
+
89
+ <LineChart
90
+ title="Team gold"
91
+ subtitle="Ranked solo queue · patch 14.7"
92
+ data={timeline}
93
+ x={(row) => row.minute}
94
+ series={[
95
+ { key: "blue", label: "Blue side", value: (row) => row.blueGold },
96
+ { key: "red", label: "Red side", value: (row) => row.redGold },
97
+ ]}
98
+ xLabel="Minute"
99
+ yLabel="Gold"
100
+ formatY={(gold) => `${Math.round(gold / 1000)}k`}
101
+ />;
102
+ ```
103
+
104
+ The chart styles ship inside `@lolmath/ui/css`, so there is no second
105
+ stylesheet to import. The fonts matter for more than looks here: the chart
106
+ engine measures text against the computed font of its own container, so axis
107
+ layout is only right once Beaufort and Spiegel have loaded.
108
+
109
+ | Component | For |
110
+ | --- | --- |
111
+ | `LineChart` | A measure read left to right. `curve`, `points`, `area`. |
112
+ | `AreaChart` | A composition over time. `stacked`, `normalize`. |
113
+ | `BarChart` | Columns over categories. `layout="grouped" \| "stacked"`. |
114
+ | `RankingChart` | The leaderboard shape: ranked horizontal bars, values at the tip. |
115
+ | `HextechChart` | Your own `defineChart` definition, themed. |
116
+ | `ChartFrame` | The panel: gold hairline, diamond corners, Beaufort title. |
117
+ | `ChartLegend` | Diamond swatches and text in ink. |
118
+
119
+ Every chart takes its data wide — one row per x position — plus a `series`
120
+ array that reads each measure out of a row, so nothing has to be reshaped
121
+ before it is plotted.
122
+
123
+ ### What they do without being asked
124
+
125
+ - **A legend for two or more series, never for one.** Colour is never the only
126
+ way to tell two series apart; a lone swatch would only restate the title.
127
+ - **A tooltip, and a crosshair on line and area charts.** An HTML chart is
128
+ interactive, so the hover layer is not opt-in.
129
+ - **Formatters reach everywhere.** `formatY` lands on the axis, the crosshair
130
+ label and the tooltip from one place.
131
+ - **Gaps, not outlines.** Bars are separated by a two-pixel channel of surface
132
+ and dots carry a two-pixel surface ring — a stroke around a mark is ink that
133
+ carries no data.
134
+
135
+ ### Writing your own
136
+
137
+ `defineChart`, the marks, the scales and the tooltip extension are re-exported
138
+ from `@lolmath/ui/charts`, so a chart shape the library does not ship still
139
+ needs only one import path. Wrap the result in `HextechChart` for the theme and
140
+ `ChartFrame` for the panel.
141
+
142
+ ```tsx
143
+ import {
144
+ barY,
145
+ ChartFrame,
146
+ defineChart,
147
+ HextechChart,
148
+ ruleY,
149
+ scaleBand,
150
+ scaleLinear,
151
+ tooltip,
152
+ } from "@lolmath/ui/charts";
153
+
154
+ const chart = defineChart({
155
+ marks: [
156
+ barY(rows, {
157
+ x: (row) => row.minute,
158
+ y: (row) => row.lead,
159
+ fill: (row) =>
160
+ row.lead >= 0
161
+ ? "var(--lol-chart-positive)"
162
+ : "var(--lol-chart-negative)",
163
+ }),
164
+ ruleY([0]),
165
+ ],
166
+ x: { scale: () => scaleBand<number>().domain(minutes) },
167
+ y: { scale: scaleLinear, nice: true, grid: true },
168
+ tooltip,
169
+ });
170
+
171
+ <ChartFrame title="Gold lead">
172
+ <HextechChart definition={chart} height={260} ariaLabel="Gold lead by minute" />
173
+ </ChartFrame>;
174
+ ```
175
+
176
+ `withHextechTheme` does the same for a definition you render yourself.
177
+
178
+ ### The Hextech mapping
179
+
180
+ The look follows the rules Riot set out in
181
+ [The Visual Language of Hextech](https://nexus.leagueoflegends.com/en-us/2016/12/the-visual-language-of-hextech/):
182
+ metal linework *frames* information and hextech magic *is* the information. So
183
+ gold carries the frame, the axes and the grid at low opacity, and the marks are
184
+ the only saturated thing on the panel. The square, the diamond and the circle
185
+ keep their stated jobs — square bar ends and a rectangular frame, diamonds at
186
+ the corners and in the legend, a circle for the focused reading. The storybook's
187
+ **Charts → The Hextech language** page walks through it in full.
188
+
189
+ ### The palette
190
+
191
+ Six categorical slots, assigned in order and never cycled:
192
+
193
+ | Slot | Name | Value |
194
+ | --- | --- | --- |
195
+ | 1 | Hextech teal | `#0aa89b` |
196
+ | 2 | Piltover gold | `#ba8c2e` |
197
+ | 3 | Arcane violet | `#8e6be8` |
198
+ | 4 | Ruin red | `#e8574f` |
199
+ | 5 | Rift azure | `#1e8fd5` |
200
+ | 6 | Chemtech green | `#4fa83a` |
201
+
202
+ Slots 1 and 2 are the Hextech signature; the rest reach further into Runeterra,
203
+ because a palette that stayed inside gold and teal could not be told apart.
204
+
205
+ The set is checked against the hextech-black surface for the OKLCH lightness
206
+ band, the chroma floor, protanopia and deuteranopia separation between
207
+ neighbouring slots, and 3:1 contrast. It clears all four for bars, lines and
208
+ areas. Scatter and bubble forms are held to the harder all-pairs test and clear
209
+ it for the **first three slots only** — past three series there, facet rather
210
+ than reach for a fourth colour. Re-run those checks before changing a value.
211
+
212
+ `--lol-chart-positive` and `--lol-chart-negative` sit outside the categorical
213
+ set and stay reserved for series that *mean* good or bad.
214
+
215
+ Everything is a `--lol-chart-*` custom property alongside the rest of the
216
+ design tokens, and TanStack's own `--ts-chart-*` variables are bridged to them,
217
+ so a hand-written definition that never sets a theme still picks up the palette
218
+ and the tooltip styling.
219
+
220
+ ### Limits worth knowing
221
+
222
+ - **No time scale.** TanStack Charts ships band, point, linear and ordinal
223
+ scales. Map a date to a number or to a pre-formatted label before it reaches
224
+ a chart; `ChartXValue` is `string | number` for that reason.
225
+ - **Never a dual axis.** Two measures of different scale are two charts, small
226
+ multiples, or one series indexed to a common base.
227
+ - **Overlaid areas stop being honest past three series.** Stack them, or facet.
228
+
73
229
  ## Client-side Routing
74
230
 
75
231
  See [react-aria-components](https://react-spectrum.adobe.com/react-aria/routing.html#app-router)