panelui-web 0.0.0-stage → 0.2.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 +43 -2
- package/dist/components/bar-picker/index.d.ts +61 -0
- package/dist/components/bar-picker/index.js +139 -0
- package/dist/components/card/index.d.ts +107 -0
- package/dist/components/card/index.js +88 -0
- package/dist/components/command-dock/index.d.ts +118 -0
- package/dist/components/command-dock/index.js +239 -0
- package/dist/components/day-grid/index.d.ts +87 -0
- package/dist/components/day-grid/index.js +251 -0
- package/dist/components/feedback/index.d.ts +135 -0
- package/dist/components/feedback/index.js +283 -0
- package/dist/components/flow/flow-paths.d.ts +94 -0
- package/dist/components/flow/flow-paths.js +270 -0
- package/dist/components/flow/index.d.ts +235 -0
- package/dist/components/flow/index.js +597 -0
- package/dist/components/frame/index.d.ts +189 -0
- package/dist/components/frame/index.js +181 -0
- package/dist/components/kpi/index.d.ts +211 -0
- package/dist/components/kpi/index.js +243 -0
- package/dist/components/lane-board/index.d.ts +125 -0
- package/dist/components/lane-board/index.js +691 -0
- package/dist/components/planet/index.d.ts +132 -0
- package/dist/components/planet/index.js +653 -0
- package/dist/components/planet/land.d.ts +8 -0
- package/dist/components/planet/land.js +122 -0
- package/dist/components/range-meter/index.d.ts +70 -0
- package/dist/components/range-meter/index.js +170 -0
- package/dist/components/rolling-number/index.d.ts +46 -0
- package/dist/components/rolling-number/index.js +81 -0
- package/dist/components/sheet/index.d.ts +148 -0
- package/dist/components/sheet/index.js +330 -0
- package/dist/components/tilt-card/index.d.ts +60 -0
- package/dist/components/tilt-card/index.js +114 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/lib/cn.d.ts +3 -0
- package/dist/lib/cn.js +6 -0
- package/package.json +72 -4
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Generated by scripts/generate-land.mjs from Natural Earth's 1:110m land polygons,
|
|
2
|
+
// which are in the public domain. Do not edit.
|
|
3
|
+
/** Cells per row and rows in the grid: one cell per degree. */
|
|
4
|
+
export const LAND_COLUMNS = 360;
|
|
5
|
+
export const LAND_ROWS = 180;
|
|
6
|
+
/**
|
|
7
|
+
* Where the land is. One bit per cell, row 0 northernmost and column 0
|
|
8
|
+
* westernmost, packed eight to a byte from the low bit and base64 encoded.
|
|
9
|
+
*/
|
|
10
|
+
export const LAND = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
11
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
12
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
13
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADg' +
|
|
14
|
+
'/wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA/v8/AOD//z8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
15
|
+
'AAAAAAAAAAAAAAD+///D/////5//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAH/j/+//////8PAAAAgB0A' +
|
|
16
|
+
'AHgAAAAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAAOD//wP8//////8BAACAfx4AAAAAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAA4' +
|
|
17
|
+
'+I73//n///////8AAAAAfwAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAOAQACDwP4D///////8BAAAAPAQAAAAAAAAAAAAY' +
|
|
18
|
+
'AAAAAAAAAAAAAAAAAAAAADzAgPP5H8D//////38AAAAAAAAAAAAA4AEAAAD+DwAAAAAAAAAAAAAAAAAAAIC/g7PfBwAA/v//' +
|
|
19
|
+
'//8AAAAAAAAAAADgBwAAwP//PwAA4B8AAAAAAAAAAAAAAAAMAAD8AgAA+P////8AAAAAAAAAAABwAAAA/P//AwAAAAAAAAAA' +
|
|
20
|
+
'AAAAAAAAAP8Ahvc5OwAA8P///38AAAAAAAAAAAAcAADg////34cHAAcAAAAAAAAAAAAAgN9/xzf8RwAA4P///z8AAAAAAAAA' +
|
|
21
|
+
'AAAPAB7g//////8fgB8AAAAAAQAAAAAAAO//B3f8/wsA4P///z8AAAAAAAAAAAAPAG////////8f2f8HAAAAAAD4HwAAAAD8' +
|
|
22
|
+
'H/D4/38A4P///xMAAAAAAPAHAAAAgN//////////////DwAAAAD+///h/4//H+/Bg/8BwP7//w8AAAAAwP8fAAAAj9//////' +
|
|
23
|
+
'/////////88/AeD///////8DB8TPB/4AAP///wAAAAAA8P//AwDjf77/////////////////DwD////////////vh/kPwP//' +
|
|
24
|
+
'BwAAAAAA/P//H/P//5//////////////////34H8////////////Afw/gP//AQAAAAAA/v+fH////+//////////////////' +
|
|
25
|
+
'/vD///////////9fAPwYgP8PAOA/AAAA/+N/8P//////////////////////QID4///////////Pw/0DAP8HAMAfAACA//F/' +
|
|
26
|
+
'/v////////////////////8/AAP4///////////BBPAHAP4HAAAAAADg//z///////////////////////9/AID/////////' +
|
|
27
|
+
'//8AQ8ABAPwBAAAAAAD8P/7///////////////////////9fAMD//////////z8AwA8AAPgBAAAAAAD+H/7/////////////' +
|
|
28
|
+
'////////Of8BAID///z//////z8AwD8AAMABAAAAAAD+P/z///////////////////9/wH8AAAD8MwD//////x8AwH8MAAAA' +
|
|
29
|
+
'AAAAAAD+P2D///////////////////95cAAAAACAAwD8/////38AwP8eAAAAAAAAgAGcH/D//////////////////wEAOAAA' +
|
|
30
|
+
'AADADgDA/////38AgP8/AAAAAAAAwANAH/T//////////////////wAAfgAAAAAwAAAA//////8PgP8/AAAAAAAAwAFwD/7/' +
|
|
31
|
+
'////////////////PwAAfwAAAAAGAAAA//////8/wP//AAAAAAAAwAOwA/7/////////////////DwCAPwAAAIAAAAAA/v//' +
|
|
32
|
+
'////8///BwAAAAAAOAcgcP//////////////////HwAAHwAAAAAAAACA+P//////4///DwAAAAAAOA74////////////////' +
|
|
33
|
+
'/////wUADwAAAAAAAAAA8P//////4///DwAAAAAAGD///////////////////////wUAAwAAAAAAAAAA8P//////7///FwAA' +
|
|
34
|
+
'AAAAAB///////////////////////wUAAgAAAAAAAAAA0P//////////CAAAAAAAgOH//////////////////////w0AAAAA' +
|
|
35
|
+
'AAAAAAAAYP////////8xLAAAAAAAAPz//////////////////////wwAAAAAAAAAAAAAAP7//////38HfgAAAAAAgP//////' +
|
|
36
|
+
'/////////////////wQAAAAAAAAAAAAAAP3///////8HUAAAAAAAAP7/////////////////////fwQAAAAAAAAAAAAAAP//' +
|
|
37
|
+
'//////+XAAAAAAAAAPz///93/P//////////////PwwAAAAAAAAAAAAAAP////////9/AAAAAAAAAPj//f9j/v//////////' +
|
|
38
|
+
'////HwAAAAAAAAAAAAAAAP////////8MAAAAAAAAAPj/+P8B/P//////////////DwQAAAAAAAAAAAAAAP///////z8AAAAA' +
|
|
39
|
+
'AAAAcPzH8/8A8P//////////////Bx4AAAAAAAAAAAAAAP///////x8AAAAAAAAA+H+Aw/8AwP////////////9/AA8AAAAA' +
|
|
40
|
+
'AAAAAAAAAP///////x8AAAAAAAAA+D8Aj//w4f////////////8/AAAAAAAAAAAAAAAAAP///////wMAAAAAAAAA+B8wuE/+' +
|
|
41
|
+
'/////////////98fAAMAAAAAAAAAAAAAAP///////wMAAAAAAAAA+A8wEMf//////////////0cOAAMAAAAAAAAAAAAAAP7/' +
|
|
42
|
+
'/////wEAAAAAAAAA+A8AAI7//////////////wMOAAEAAAAAAAAAAAAAAP7//////wAAAAAAAAAA+AcABob/////////////' +
|
|
43
|
+
'/zccwAEAAAAAAAAAAAAAAPz//////wAAAAAAAAAAQOB/AAQ2/////////////x8c8AEAAAAAAAAAAAAAAPj//////wAAAAAA' +
|
|
44
|
+
'AAAAQPx/AAAA/////////////w8Y/gEAAAAAAAAAAAAAAPD/////fwAAAAAAAAAA4P8/AAAA/////////////w+EGwAAAAAA' +
|
|
45
|
+
'AAAAAAAAAMD/////HwAAAAAAAAAA8P9/AACA/////////////x/AAwAAAAAAAAAAAAAAAID/////DwAAAAAAAAAA+P//BwaA' +
|
|
46
|
+
'/////////////x/AAAAAAAAAAAAAAAAAAID9////BwAAAAAAAAAA/P//D3+E/////////////z9AAAAAAAAAAAAAAAAAAAD5' +
|
|
47
|
+
'////BwAAAAAAAAAA/P//f////////////////x8AAAAAAAAAAAAAAAAAAADy/x8GBgAAAAAAAAAA/P/////v/8//////////' +
|
|
48
|
+
'/z8AAAAAAAAAAAAAAAAAAAD0/w8ABgAAAAAAAAAA/v////+//4///////////z8AAAAAAAAAAAAAAAAAAADu/wcADgAAAAAA' +
|
|
49
|
+
'AACA//////8f/x/+/////////x8AAAAAAAAAAAAAAAAAAACI/wcALAAAAAAAAADA//////8//z/g/////////w8AAAAAAAAA' +
|
|
50
|
+
'AAAAAAAAAACQ/wcACAAAAAAAAADg//////9//r+A/////////wcAAAAAAAAAAAAAAAAAAAAQ/wMAAAAAAAAAAADg//////9/' +
|
|
51
|
+
'/n8cgP///////ycAAAAAAAAAAAAAAAAAAAAA/gMAAAAAAAAAAADw////////+P9/AP///////xEAAAAAAAAAAAAAAAAAAAAA' +
|
|
52
|
+
'/AMAHQAAAAAAAADw////////+P//APz/f///fxAAAAAAAAAAAAAAAAAAAAAA+AcAYAAAAAAAAAD4////////+f9/APz/B///' +
|
|
53
|
+
'BgAAAAAAAAAAAAAAAAAAAAAA/AccgAEAAAAAAADw////////8f9/AOD/B/5/AAAAAAAAAAAAAAAAAQAAAAAA+A8eADgAAAAA' +
|
|
54
|
+
'AADw////////4f8/AOD/Afw/BgAAAAAAAAAAAAAAAAAAAAAA8J8PAPwCAAAAAADw////////4/8fAOD/APw/AgAAAAAAAAAA' +
|
|
55
|
+
'AAAAAAAAAAAAgP8PAAAAAAAAAADw////////x/8HAOB/APx/ADAAAAAAAAAAAAAAAAAAAAAAAP4PAAAAAAAAAAD4////////' +
|
|
56
|
+
'h/8BAOA/APz/ADAAAAAAAAAAAAAAAAAAAAAAAID/AAAAAAAAAAD4////////j/8AAMAPAMD/ATAAAAAAAAAAAAAAAAAAAAAA' +
|
|
57
|
+
'AAD/AQAAAAAAAAD4////////nx8AAMAPAMD/ATAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAAAAAD4////////vwcAAIAPAMD/' +
|
|
58
|
+
'AcAAAAAAAAAAAAAAAAAAAAAAAADgAQAAAAAAAAD4////////fwAAAIAPAMD8AQABAAAAAAAAAAAAAAAAAAAAAADAAAgAAAAA' +
|
|
59
|
+
'AADw////////f2AAAAAPAID4AUACAAAAAAAAAAAAAAAAAAAAAADAAe9zAAAAAADg/////////34AAAAPAEBwAAgAAAAAAAAA' +
|
|
60
|
+
'AAAAAAAAAAAAAAAAE+9/AAAAAADA/////////38AAAAXAEAgAAQCAAAAAAAAAAAAAAAAAAAAAAAAz///AAAAAACA////////' +
|
|
61
|
+
'/z8AAAASAMAAAIACAAAAAAAAAAAAAAAAAAAAAAAAyP//AQAAAACA/////////z8AAAAwAIAAAEAHAAAAAAAAAAAAAAAAAAAA' +
|
|
62
|
+
'AAAAgP//AwAAAAAA/v///////x8AAAAwAAADAAMBAAAAAAAAAAAAAAAAAAAAAAAAwP//fwAAAAAA/A/+/////x8AAAAAAAAH' +
|
|
63
|
+
'AAcAAAAAAAAAAAAAAAAAAAAAAAAAgP///wAAAAAAEAD8/////w8AAAAAADAGwAcAAAAAAAAAAAAAAAAAAAAAAAAAgP///wEA' +
|
|
64
|
+
'AAAAAADA/////wcAAAAAAGAG4AEAAAAAAAAAAAAAAAAAAAAAAAAA4P///wEAAAAAAADA/////wMAAAAAAMAM+AMAAAAAAAAA' +
|
|
65
|
+
'AAAAAAAAAAAAAAAA4P///wMAAAAAAADg/////wEAAAAAAIAL/gMQAAAAAAAAAAAAAAAAAAAAAAAA8P///wMAAAAAAADg////' +
|
|
66
|
+
'fwAAAAAAAIAH/vMQAAAAAAAAAAAAAAAAAAAAAAAA8P///wcAAAAAAADg////PwAAAAAAAAAP/gMAAQAAAAAAAAAAAAAAAAAA' +
|
|
67
|
+
'AAAA+P///38AAAAAAADg////PwAAAAAAAAAO/DmAAwAAAAAAAAAAAAAAAAAAAAAA+P///38BAAAAAADA////HwAAAAAAAAA+' +
|
|
68
|
+
'/DkA8gAAAAAAAAAAAAAAAAAAAAAA8P////8fAAAAAACA////DwAAAAAAAAA8wChE/gcAAAAAAAAAAAAAAAAAAAAA+P////8/' +
|
|
69
|
+
'AAAAAACA////BwAAAAAAAAA4AEAA8B8AAAAAAAAAAAAAAAAAAAAA+P//////AQAAAAAA////BwAAAAAAAAAwAEAAxD8NAAAA' +
|
|
70
|
+
'AAAAAAAAAAAAAAAA+P//////AQAAAAAA////BwAAAAAAAADAAQAAxP+AAAAAAAAAAAAAAAAAAAAA8P//////AQAAAAAA/v//' +
|
|
71
|
+
'BwAAAAAAAACAHwAAwH8ABAAAAAAAAAAAAAAAAAAA4P//////AQAAAAAA/v//BwAAAAAAAAAAQFYEAMcAAAAAAAAAAAAAAAAA' +
|
|
72
|
+
'AAAA4P//////AAAAAAAA/v//DwAAAAAAAAAAAAgBAIABMAAAAAAAAAAAAAAAAAAAwP//////AAAAAAAA/v//DwAAAAAAAAAA' +
|
|
73
|
+
'AAAAAAAEAAAAAAAAAAAAAAAAAAAAgP////9/AAAAAAAA/P//DwAAAAAAAAAAAAAAAQQAAAAAAAAAAAAAAAAAAAAAgP////8/' +
|
|
74
|
+
'AAAAAAAA/v//HyAAAAAAAAAAAACAHwQAAAAAAAAAAAAAAAAAAAAAAP////8fAAAAAAAA/v//HyAAAAAAAAAAAADADwwAAAAA' +
|
|
75
|
+
'AAAAAAAAAAAAAAAAAP////8fAAAAAAAA////HzAAAAAAAAAAAADsDxwAAAAAAAAAAAAAAAAAAAAAAP7///8fAAAAAAAA////' +
|
|
76
|
+
'DzgAAAAAAAAAAAD/DxwAAAAAAAAAAAAAAAAAAAAAAPj///8fAAAAAAAA////Dz8AAAAAAAAAAAD/Pz4AAAiAAAAAAAAAAAAA' +
|
|
77
|
+
'AAAAAOD///8fAAAAAAAA////Ax8AAAAAAAAAAMD//z4AAABAAAAAAAAAAAAAAAAAAMD///8PAAAAAAAA////AB8AAAAAAAAA' +
|
|
78
|
+
'AMD//z8AAAAAAAAAAAAAAAAAAAAAAMD///8PAAAAAAAA/v9/AB8AAAAAAAAAAOD//38AAAAAAAAAAAAAAAAAAAAAAMD///8P' +
|
|
79
|
+
'AAAAAAAA/v9/AB8AAAAAAAAAAPz///8BAAEAAAAAAAAAAAAAAAAAAMD///8HAAAAAAAA/P9/gA8AAAAAAAAAgP////8BAAIA' +
|
|
80
|
+
'AAAAAAAAAAAAAAAAAMD///8DAAAAAAAA/P//gA8AAAAAAAAAwP////8DAAAAAAAAAAAAAAAAAAAAAMD//38AAAAAAAAA/P9/' +
|
|
81
|
+
'AA8AAAAAAAAAwP////8HAAAAAAAAAAAAAAAAAAAAAOD//x8AAAAAAAAA+P9/AAcAAAAAAAAA4P////8PAAAAAAAAAAAAAAAA' +
|
|
82
|
+
'AAAAAOD//w8AAAAAAAAA+P8fAAIAAAAAAAAAwP////8fAAAAAAAAAAAAAAAAAAAAAOD//wcAAAAAAAAA+P8fAAAAAAAAAAAA' +
|
|
83
|
+
'4P////8fAAAAAAAAAAAAAAAAAAAAAOD//wcAAAAAAAAA+P8fAAAAAAAAAAAAwP////8fAAAAAAAAAAAAAAAAAAAAAOD//wcA' +
|
|
84
|
+
'AAAAAAAA8P8PAAAAAAAAAAAAgP////8/AAAAAAAAAAAAAAAAAAAAAOD//wMAAAAAAAAA4P8HAAAAAAAAAAAAgP////8fAAAA' +
|
|
85
|
+
'AAAAAAAAAAAAAAAAAPD//wMAAAAAAAAA4P8HAAAAAAAAAAAAgP////8fAAAAAAAAAAAAAAAAAAAAAPD//wEAAAAAAAAAwP8D' +
|
|
86
|
+
'AAAAAAAAAAAAAP////8fAAAAAAAAAAAAAAAAAAAAAOD//wAAAAAAAAAAwP8BAAAAAAAAAAAAAP8D/P8PAAAAAAAAAAAAAAAA' +
|
|
87
|
+
'AAAAAPD/fwAAAAAAAAAAwH8AAAAAAAAAAAAAgP8A2P8HAAAAAAAAAAAAAAAAAAAAAPD/OwAAAAAAAAAAgAEAAAAAAAAAAAAA' +
|
|
88
|
+
'AAcA6P8HAAAAAAAAAAAAAAAAAAAAAPj/BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwP8DAAACAAAAAAAAAAAAAAAAAPj/BwAA' +
|
|
89
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8DAAAEAAAAAAAAAAAAAAAAAPz/BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8DAAAI' +
|
|
90
|
+
'AAAAAAAAAAAAAAAAAPj/AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGgAAAA4AAAAAAAAAAAAAAAAAPg/AAAAAAAAAAAAAAAA' +
|
|
91
|
+
'AAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAAAPw/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAA' +
|
|
92
|
+
'AAAAAPwHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAALAAAAAAAAAAAAAAAAAPwfAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
93
|
+
'AAAAAOAAAIADAAAAAAAAAAAAAAAAAPgHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAMABAAAAAAAAAAAAAAAAAPwHAAAA' +
|
|
94
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAP4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgA' +
|
|
95
|
+
'AAAAAAAAAAAAAAAAAP4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAP4DAAAAAAAAAAAAAAAA' +
|
|
96
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
97
|
+
'AAAAAP8AAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
98
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAH6AAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAA' +
|
|
99
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
100
|
+
'AAAAAAAAAAAAAAAAAPADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAA' +
|
|
101
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
102
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
103
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
104
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
105
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAA' +
|
|
106
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
107
|
+
'AAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAIAfAAAAAAAe' +
|
|
108
|
+
'eADADwAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAPD/AADA/////////z8AAAAAAAAAAAAAAAAAAAAAAAAHAAAA' +
|
|
109
|
+
'AAAAAAAAAAAAgP///wH4//////////8DAAAAAAAAAAAAAAAAAAAAADAfAAAAAAAAAAAAAADw8////wP+////////////AwAA' +
|
|
110
|
+
'AAAAAAAAAAAAAAAAAHA/AAAAAAAAAAA8//P//////+D/////////////HwAAAAAAAAAAAAAAAAAAAP5+AAAAAAAA5v//////' +
|
|
111
|
+
'//////D//////////////38AAAAAAAAAAAAAAAAAAAB+AAAAAACA/////////////////////////////38AAAAAAAAAYAYA' +
|
|
112
|
+
'4ON/OPh/AAAAAADw/////////////////////////////x8AAAAAAADwv/MXgP////8fAAAAAADw////////////////////' +
|
|
113
|
+
'/////////wMAAAAAAPz///////////8HAAAAAAD+/////////////////////////////wAAAAAAAPz//////////z8AAAAA' +
|
|
114
|
+
'AP///////////////////////////////wAAAADA/////////////wEAAAAA8P///////////////////////////////wAA' +
|
|
115
|
+
'AACH////////////PwAAAPgA/////////////////////////////////wcAAAAcgP//////////fwAAAP4BwP//////////' +
|
|
116
|
+
'////////////////////HwAAAAAAAP7//////////wf8wD8AwP//////////////////////////////DwAAAAAA/v//////' +
|
|
117
|
+
'//////8HAAD4////////////////////////////////HwAAAAAA+P//////////////4f//////////////////////////' +
|
|
118
|
+
'/////////wAAAAAA/P///////////////////////////////////////////////////x8A3x8AAP7/////////////////' +
|
|
119
|
+
'//////////////////////////////////8/AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
120
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
121
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
122
|
+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RangeMeter — where a reading sits between its lowest and its highest, on a
|
|
3
|
+
* ruler of ticks that rises under the value.
|
|
4
|
+
*
|
|
5
|
+
* ```tsx
|
|
6
|
+
* <RangeMeter min={80} max={600} value={time} onValueChange={setTime} marker={300} />
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* ## The ruler swells where the reading is
|
|
10
|
+
*
|
|
11
|
+
* The track is a row of ticks. The ones up to the reading are lit, and the
|
|
12
|
+
* ticks round the reading stand taller, falling away on either side, so the
|
|
13
|
+
* value is a rise in the ruler and not a knob sitting on a line. Dragging
|
|
14
|
+
* moves the rise along, and the ticks it leaves settle back behind it.
|
|
15
|
+
*
|
|
16
|
+
* ## The number rides above
|
|
17
|
+
*
|
|
18
|
+
* The value is written in a tag over the rise, so the place on the ruler and
|
|
19
|
+
* the figure are one thing to read. The tag is hung from the point it marks
|
|
20
|
+
* in proportion to how far along that point is: from its left edge at the low
|
|
21
|
+
* end, its middle half way, its right edge at the high end. It is always over
|
|
22
|
+
* the reading and never outside the ruler.
|
|
23
|
+
*
|
|
24
|
+
* ## A reading or a control
|
|
25
|
+
*
|
|
26
|
+
* With `onValueChange` it is a slider: drag it, or use the arrow keys. Without
|
|
27
|
+
* it, the same drawing is a meter that shows a value and cannot be moved.
|
|
28
|
+
* Either way a screen reader is given the value and the range it is in.
|
|
29
|
+
*
|
|
30
|
+
* ## The colour is the theme's
|
|
31
|
+
*
|
|
32
|
+
* `color` names one of the theme's roles. Changing it as the value crosses a
|
|
33
|
+
* threshold is how a meter says the reading has gone from fine to not.
|
|
34
|
+
*/
|
|
35
|
+
import { type ComponentProps, type ReactNode } from 'react';
|
|
36
|
+
/** Which of the theme's roles the lit ticks and the tag are drawn in. */
|
|
37
|
+
export type RangeMeterColor = 'primary' | 'info' | 'success' | 'warning' | 'destructive';
|
|
38
|
+
export interface RangeMeterProps extends Omit<ComponentProps<'div'>, 'onChange' | 'color'> {
|
|
39
|
+
/** The low end of the ruler. */
|
|
40
|
+
min?: number;
|
|
41
|
+
/** The high end of the ruler. */
|
|
42
|
+
max?: number;
|
|
43
|
+
/** The reading: where the ruler rises and the number written above it. */
|
|
44
|
+
value: number;
|
|
45
|
+
/**
|
|
46
|
+
* Called with the new value as the ruler is dragged or moved with the
|
|
47
|
+
* keyboard. Passing this makes it a slider; without it, it is a meter.
|
|
48
|
+
*/
|
|
49
|
+
onValueChange?: (value: number) => void;
|
|
50
|
+
/** How much one arrow key press moves the value, and what a drag snaps to. */
|
|
51
|
+
step?: number;
|
|
52
|
+
/** A value to mark under the ruler, such as a target. */
|
|
53
|
+
marker?: number;
|
|
54
|
+
/** How many ticks the ruler is drawn with. */
|
|
55
|
+
ticks?: number;
|
|
56
|
+
/** Which of the theme's roles the lit ticks and the tag are drawn in. */
|
|
57
|
+
color?: RangeMeterColor;
|
|
58
|
+
/** Writes the number in the tag. Defaults to the value as it is. */
|
|
59
|
+
formatValue?: (value: number) => ReactNode;
|
|
60
|
+
/** What a screen reader calls it. */
|
|
61
|
+
label?: string;
|
|
62
|
+
/** What a screen reader says for the value: "240 milliseconds". */
|
|
63
|
+
valueText?: string;
|
|
64
|
+
/** Drawn in the tag before the number: an icon. */
|
|
65
|
+
children?: ReactNode;
|
|
66
|
+
}
|
|
67
|
+
export declare function RangeMeter({ min, max, value, onValueChange, step, marker, ticks, color, formatValue, label, valueText, className, children, ...props }: RangeMeterProps): import("react").JSX.Element;
|
|
68
|
+
export declare namespace RangeMeter {
|
|
69
|
+
var displayName: string;
|
|
70
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* RangeMeter — where a reading sits between its lowest and its highest, on a
|
|
5
|
+
* ruler of ticks that rises under the value.
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* <RangeMeter min={80} max={600} value={time} onValueChange={setTime} marker={300} />
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* ## The ruler swells where the reading is
|
|
12
|
+
*
|
|
13
|
+
* The track is a row of ticks. The ones up to the reading are lit, and the
|
|
14
|
+
* ticks round the reading stand taller, falling away on either side, so the
|
|
15
|
+
* value is a rise in the ruler and not a knob sitting on a line. Dragging
|
|
16
|
+
* moves the rise along, and the ticks it leaves settle back behind it.
|
|
17
|
+
*
|
|
18
|
+
* ## The number rides above
|
|
19
|
+
*
|
|
20
|
+
* The value is written in a tag over the rise, so the place on the ruler and
|
|
21
|
+
* the figure are one thing to read. The tag is hung from the point it marks
|
|
22
|
+
* in proportion to how far along that point is: from its left edge at the low
|
|
23
|
+
* end, its middle half way, its right edge at the high end. It is always over
|
|
24
|
+
* the reading and never outside the ruler.
|
|
25
|
+
*
|
|
26
|
+
* ## A reading or a control
|
|
27
|
+
*
|
|
28
|
+
* With `onValueChange` it is a slider: drag it, or use the arrow keys. Without
|
|
29
|
+
* it, the same drawing is a meter that shows a value and cannot be moved.
|
|
30
|
+
* Either way a screen reader is given the value and the range it is in.
|
|
31
|
+
*
|
|
32
|
+
* ## The colour is the theme's
|
|
33
|
+
*
|
|
34
|
+
* `color` names one of the theme's roles. Changing it as the value crosses a
|
|
35
|
+
* threshold is how a meter says the reading has gone from fine to not.
|
|
36
|
+
*/
|
|
37
|
+
import { useCallback, useRef, } from 'react';
|
|
38
|
+
import { tv } from 'tailwind-variants';
|
|
39
|
+
const rangeMeterVariants = tv({
|
|
40
|
+
slots: {
|
|
41
|
+
// Room above the ruler for the tag, so the tag is inside the control's
|
|
42
|
+
// own box and cannot be clipped by whatever the meter sits in.
|
|
43
|
+
root: 'relative w-full touch-none select-none rounded-lg pt-9 outline-none rtl:[--pu-dir:-1] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background',
|
|
44
|
+
ruler: 'relative flex h-10 items-end justify-between',
|
|
45
|
+
tick: 'w-[3px] rounded-full transition-[height,background-color,opacity,box-shadow] duration-200 ease-out motion-reduce:transition-none',
|
|
46
|
+
marker: 'pointer-events-none absolute -bottom-2.5 size-1.5 -translate-x-1/2 rotate-45 rounded-[1px] bg-foreground rtl:translate-x-1/2',
|
|
47
|
+
tag: 'pointer-events-none absolute top-0 flex h-7 items-center gap-1 whitespace-nowrap rounded-full bg-[var(--pu-color)] px-2.5 text-xs font-semibold tabular-nums text-[var(--pu-on-color)] shadow-[0_6px_18px_-4px_color-mix(in_oklab,var(--pu-color)_70%,transparent)] transition-[background-color,box-shadow,scale] duration-200 motion-reduce:transition-none [&_svg]:size-3.5 [&_svg]:shrink-0',
|
|
48
|
+
},
|
|
49
|
+
variants: {
|
|
50
|
+
/*
|
|
51
|
+
* A role and what is written on it. `primary` has a partner in every
|
|
52
|
+
* theme. The status roles are mid-tones, light enough that the theme
|
|
53
|
+
* writes on them in near-black whether the page is light or dark.
|
|
54
|
+
*/
|
|
55
|
+
color: {
|
|
56
|
+
primary: { root: '[--pu-color:var(--primary)] [--pu-on-color:var(--primary-foreground)]' },
|
|
57
|
+
info: { root: '[--pu-color:var(--info)] [--pu-on-color:var(--color-neutral-900)]' },
|
|
58
|
+
success: { root: '[--pu-color:var(--success)] [--pu-on-color:var(--color-neutral-900)]' },
|
|
59
|
+
warning: { root: '[--pu-color:var(--warning)] [--pu-on-color:var(--color-neutral-900)]' },
|
|
60
|
+
destructive: {
|
|
61
|
+
root: '[--pu-color:var(--destructive)] [--pu-on-color:var(--color-neutral-900)]',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
lit: {
|
|
65
|
+
true: { tick: 'bg-[var(--pu-color)]' },
|
|
66
|
+
false: { tick: 'bg-muted-foreground/25' },
|
|
67
|
+
},
|
|
68
|
+
// The tick the reading is on, which is the top of the rise.
|
|
69
|
+
crest: {
|
|
70
|
+
true: {
|
|
71
|
+
tick: 'shadow-[0_0_10px_color-mix(in_oklab,var(--pu-color)_70%,transparent)]',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
// The marker hangs under the ruler, and the control makes room for it.
|
|
75
|
+
marked: {
|
|
76
|
+
true: { root: 'pb-4' },
|
|
77
|
+
},
|
|
78
|
+
interactive: {
|
|
79
|
+
true: { root: 'cursor-grab active:cursor-grabbing active:[&_[data-tag]]:scale-110' },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
|
|
84
|
+
/** How many ticks either side of the reading the rise spreads over. */
|
|
85
|
+
const SPREAD = 2.6;
|
|
86
|
+
/** A resting tick's height, as a share of the ruler's. */
|
|
87
|
+
const REST = 0.34;
|
|
88
|
+
export function RangeMeter({ min = 0, max = 100, value, onValueChange, step = 1, marker, ticks = 41, color = 'primary', formatValue, label = 'Reading', valueText, className, children, ...props }) {
|
|
89
|
+
const interactive = onValueChange !== undefined;
|
|
90
|
+
const slots = rangeMeterVariants({ color, interactive, marked: marker !== undefined });
|
|
91
|
+
const ruler = useRef(null);
|
|
92
|
+
const dragging = useRef(false);
|
|
93
|
+
const count = Math.max(2, Math.round(ticks));
|
|
94
|
+
const span = max - min || 1;
|
|
95
|
+
const share = clamp((value - min) / span, 0, 1);
|
|
96
|
+
// Where the reading falls among the ticks, as a position between two of
|
|
97
|
+
// them, so the rise slides along the ruler and does not hop tick to tick.
|
|
98
|
+
const at = share * (count - 1);
|
|
99
|
+
const crest = Math.round(at);
|
|
100
|
+
// Rounded to the step's own precision, so 0.1 steps do not come back as
|
|
101
|
+
// 0.30000000000000004.
|
|
102
|
+
const places = (String(step).split('.')[1] ?? '').length;
|
|
103
|
+
const settle = useCallback((raw) => clamp(Number((Math.round(raw / step) * step).toFixed(places)), min, max), [step, places, min, max]);
|
|
104
|
+
const fromPointer = useCallback((clientX) => {
|
|
105
|
+
const node = ruler.current;
|
|
106
|
+
const box = node?.getBoundingClientRect();
|
|
107
|
+
if (!node || !box || box.width === 0)
|
|
108
|
+
return;
|
|
109
|
+
const rtl = getComputedStyle(node).direction === 'rtl';
|
|
110
|
+
const along = rtl ? box.right - clientX : clientX - box.left;
|
|
111
|
+
onValueChange?.(settle(min + clamp(along / box.width, 0, 1) * span));
|
|
112
|
+
}, [min, span, settle, onValueChange]);
|
|
113
|
+
const onPointerDown = (event) => {
|
|
114
|
+
if (!interactive || event.button !== 0)
|
|
115
|
+
return;
|
|
116
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
117
|
+
dragging.current = true;
|
|
118
|
+
fromPointer(event.clientX);
|
|
119
|
+
};
|
|
120
|
+
const onPointerMove = (event) => {
|
|
121
|
+
if (dragging.current)
|
|
122
|
+
fromPointer(event.clientX);
|
|
123
|
+
};
|
|
124
|
+
const endDrag = () => {
|
|
125
|
+
dragging.current = false;
|
|
126
|
+
};
|
|
127
|
+
const onKeyDown = (event) => {
|
|
128
|
+
if (!interactive)
|
|
129
|
+
return;
|
|
130
|
+
const rtl = getComputedStyle(event.currentTarget).direction === 'rtl';
|
|
131
|
+
const page = Math.max(step, Math.round(span / 10 / step) * step);
|
|
132
|
+
const moves = {
|
|
133
|
+
ArrowLeft: value + (rtl ? step : -step),
|
|
134
|
+
ArrowRight: value + (rtl ? -step : step),
|
|
135
|
+
ArrowDown: value - step,
|
|
136
|
+
ArrowUp: value + step,
|
|
137
|
+
PageDown: value - page,
|
|
138
|
+
PageUp: value + page,
|
|
139
|
+
Home: min,
|
|
140
|
+
End: max,
|
|
141
|
+
};
|
|
142
|
+
const next = moves[event.key];
|
|
143
|
+
if (next === undefined)
|
|
144
|
+
return;
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
onValueChange(clamp(Number(next.toFixed(places)), min, max));
|
|
147
|
+
};
|
|
148
|
+
return (_jsxs("div", { role: interactive ? 'slider' : 'meter', "aria-label": label, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": value, "aria-valuetext": valueText, "aria-orientation": interactive ? 'horizontal' : undefined, tabIndex: interactive ? 0 : undefined, className: slots.root({ className }), onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: endDrag, onPointerCancel: endDrag, onKeyDown: onKeyDown, ...props, children: [_jsxs("span", { "data-tag": "", className: slots.tag(), style: {
|
|
149
|
+
insetInlineStart: `${(share * 100).toFixed(2)}%`,
|
|
150
|
+
// Hung by the same share of its own width as the point is along the
|
|
151
|
+
// ruler, which keeps it over the point and inside the ends.
|
|
152
|
+
translate: `calc(${share.toFixed(4)} * -100% * var(--pu-dir, 1))`,
|
|
153
|
+
}, children: [children, formatValue ? formatValue(value) : value] }), _jsxs("div", { ref: ruler, "aria-hidden": "true", className: slots.ruler(), children: [Array.from({ length: count }, (_, index) => {
|
|
154
|
+
const distance = (index - at) / SPREAD;
|
|
155
|
+
// A bell: full height at the reading, resting height a few ticks out.
|
|
156
|
+
const rise = Math.exp(-distance * distance);
|
|
157
|
+
const lit = index <= at + 0.001;
|
|
158
|
+
const part = rangeMeterVariants({ lit, crest: index === crest });
|
|
159
|
+
return (_jsx("span", { className: part.tick(), style: {
|
|
160
|
+
// Rounded, so the server and the browser write the same
|
|
161
|
+
// string for it and the page hydrates without a mismatch.
|
|
162
|
+
height: `${((REST + (1 - REST) * rise) * 100).toFixed(1)}%`,
|
|
163
|
+
// Lit ticks brighten towards the reading, like a tail behind it.
|
|
164
|
+
opacity: lit
|
|
165
|
+
? Number((0.35 + 0.65 * (crest === 0 ? 1 : Math.min(1, index / crest))).toFixed(3))
|
|
166
|
+
: undefined,
|
|
167
|
+
} }, index));
|
|
168
|
+
}), marker !== undefined ? (_jsx("span", { className: slots.marker(), style: { insetInlineStart: `${(clamp((marker - min) / span, 0, 1) * 100).toFixed(2)}%` } })) : null] })] }));
|
|
169
|
+
}
|
|
170
|
+
RangeMeter.displayName = 'RangeMeter';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RollingNumber — a number whose digits roll to their new values.
|
|
3
|
+
*
|
|
4
|
+
* ```tsx
|
|
5
|
+
* <RollingNumber value={events} />
|
|
6
|
+
* <RollingNumber value={balance} format={{ style: 'currency', currency: 'USD' }} />
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* Every digit is a column of ten, and the column is moved by a share of its
|
|
10
|
+
* own height. Nothing is measured: one hidden `0` in flow gives the column the
|
|
11
|
+
* width and the line box the font really has, and the ten digits stacked over
|
|
12
|
+
* it are each exactly that tall, so `translateY(-30%)` is the 3 whatever the
|
|
13
|
+
* type size turns out to be.
|
|
14
|
+
*
|
|
15
|
+
* That hidden `0` is also why the number sits on the text's baseline. A box
|
|
16
|
+
* that clips its overflow takes its bottom edge as its baseline, which would
|
|
17
|
+
* lift every digit above the words beside it. Here the clipping is done by an
|
|
18
|
+
* absolutely positioned window inside the column, and the column's baseline
|
|
19
|
+
* is still the `0`'s.
|
|
20
|
+
*
|
|
21
|
+
* The columns are keyed from the decimal point outwards. Going from 99 to 100
|
|
22
|
+
* adds a hundreds column rather than re-labelling the two that were there, so
|
|
23
|
+
* the nines roll to zeroes instead of being replaced by them.
|
|
24
|
+
*/
|
|
25
|
+
import { type ComponentProps } from 'react';
|
|
26
|
+
export interface RollingNumberProps extends Omit<ComponentProps<'span'>, 'children'> {
|
|
27
|
+
/** The number to show. Each digit rolls to its new value on its own. */
|
|
28
|
+
value: number;
|
|
29
|
+
/**
|
|
30
|
+
* How the number is written: currency, percent, compact notation, how many
|
|
31
|
+
* decimals. Passed to `Intl.NumberFormat`.
|
|
32
|
+
*/
|
|
33
|
+
format?: Intl.NumberFormatOptions;
|
|
34
|
+
/**
|
|
35
|
+
* The locale the number is written in. Defaults to `en-US` rather than the
|
|
36
|
+
* browser's, so a page rendered on a server writes the same thing the
|
|
37
|
+
* browser then does.
|
|
38
|
+
*/
|
|
39
|
+
locales?: Intl.LocalesArgument;
|
|
40
|
+
/** How long a digit takes to roll, in milliseconds. */
|
|
41
|
+
duration?: number;
|
|
42
|
+
}
|
|
43
|
+
export declare function RollingNumber({ value, format, locales, duration, className, ...props }: RollingNumberProps): import("react").JSX.Element;
|
|
44
|
+
export declare namespace RollingNumber {
|
|
45
|
+
var displayName: string;
|
|
46
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* RollingNumber — a number whose digits roll to their new values.
|
|
5
|
+
*
|
|
6
|
+
* ```tsx
|
|
7
|
+
* <RollingNumber value={events} />
|
|
8
|
+
* <RollingNumber value={balance} format={{ style: 'currency', currency: 'USD' }} />
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Every digit is a column of ten, and the column is moved by a share of its
|
|
12
|
+
* own height. Nothing is measured: one hidden `0` in flow gives the column the
|
|
13
|
+
* width and the line box the font really has, and the ten digits stacked over
|
|
14
|
+
* it are each exactly that tall, so `translateY(-30%)` is the 3 whatever the
|
|
15
|
+
* type size turns out to be.
|
|
16
|
+
*
|
|
17
|
+
* That hidden `0` is also why the number sits on the text's baseline. A box
|
|
18
|
+
* that clips its overflow takes its bottom edge as its baseline, which would
|
|
19
|
+
* lift every digit above the words beside it. Here the clipping is done by an
|
|
20
|
+
* absolutely positioned window inside the column, and the column's baseline
|
|
21
|
+
* is still the `0`'s.
|
|
22
|
+
*
|
|
23
|
+
* The columns are keyed from the decimal point outwards. Going from 99 to 100
|
|
24
|
+
* adds a hundreds column rather than re-labelling the two that were there, so
|
|
25
|
+
* the nines roll to zeroes instead of being replaced by them.
|
|
26
|
+
*/
|
|
27
|
+
import { useMemo } from 'react';
|
|
28
|
+
import { tv } from 'tailwind-variants';
|
|
29
|
+
const rollingNumberVariants = tv({
|
|
30
|
+
slots: {
|
|
31
|
+
// `tabular-nums` so a 1 is as wide as an 8 and the number does not change
|
|
32
|
+
// width, and shove whatever is beside it, while it rolls.
|
|
33
|
+
root: 'inline-flex items-baseline tabular-nums',
|
|
34
|
+
column: 'relative inline-block',
|
|
35
|
+
window: 'absolute inset-0 overflow-hidden',
|
|
36
|
+
strip: 'absolute inset-x-0 top-0 block h-[1000%] transition-transform ease-[cubic-bezier(0.2,0,0,1)] will-change-transform motion-reduce:transition-none',
|
|
37
|
+
digit: 'block h-[10%] text-center',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const DIGITS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
41
|
+
/** Splits a formatted number into rolling digits and the characters around them. */
|
|
42
|
+
function piecesOf(parts) {
|
|
43
|
+
// Whole digits are counted from the decimal point, so the units column is
|
|
44
|
+
// the same column whatever is in front of it.
|
|
45
|
+
let whole = parts.reduce((count, part) => (part.type === 'integer' ? count + part.value.length : count), 0);
|
|
46
|
+
let fraction = 0;
|
|
47
|
+
const pieces = [];
|
|
48
|
+
parts.forEach((part, index) => {
|
|
49
|
+
if (part.type === 'integer' || part.type === 'fraction') {
|
|
50
|
+
for (const character of part.value) {
|
|
51
|
+
const digit = character >= '0' && character <= '9' ? Number(character) : null;
|
|
52
|
+
const key = part.type === 'integer' ? `i${(whole -= 1)}` : `f${(fraction += 1)}`;
|
|
53
|
+
pieces.push({ key, digit, text: character });
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// A separator belongs to the digits on its right; anything else is keyed
|
|
58
|
+
// by what it is, so a currency sign survives the number growing a digit.
|
|
59
|
+
const key = part.type === 'group' ? `g${whole}` : `${part.type}${index}`;
|
|
60
|
+
pieces.push({ key, digit: null, text: part.value });
|
|
61
|
+
});
|
|
62
|
+
return pieces;
|
|
63
|
+
}
|
|
64
|
+
export function RollingNumber({ value, format, locales = 'en-US', duration = 600, className, ...props }) {
|
|
65
|
+
const slots = rollingNumberVariants();
|
|
66
|
+
// The options object is usually written inline, so it is new on every
|
|
67
|
+
// render. Its contents are what the formatter depends on.
|
|
68
|
+
const formatKey = JSON.stringify(format ?? null);
|
|
69
|
+
const formatter = useMemo(() => new Intl.NumberFormat(locales, format),
|
|
70
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
71
|
+
[locales, formatKey]);
|
|
72
|
+
const { label, pieces } = useMemo(() => {
|
|
73
|
+
const parts = formatter.formatToParts(value);
|
|
74
|
+
return { label: parts.map((part) => part.value).join(''), pieces: piecesOf(parts) };
|
|
75
|
+
}, [formatter, value]);
|
|
76
|
+
return (_jsxs("span", { className: slots.root({ className }), ...props, children: [_jsx("span", { className: "sr-only", children: label }), pieces.map((piece) => piece.digit === null ? (_jsx("span", { "aria-hidden": "true", children: piece.text }, piece.key)) : (_jsxs("span", { "aria-hidden": "true", className: slots.column(), children: [_jsx("span", { className: "invisible", children: "0" }), _jsx("span", { className: slots.window(), children: _jsx("span", { className: slots.strip(), style: {
|
|
77
|
+
transform: `translateY(${-piece.digit * 10}%)`,
|
|
78
|
+
transitionDuration: `${duration}ms`,
|
|
79
|
+
}, children: DIGITS.map((digit) => (_jsx("span", { className: slots.digit(), children: digit }, digit))) }) })] }, piece.key)))] }));
|
|
80
|
+
}
|
|
81
|
+
RollingNumber.displayName = 'RollingNumber';
|