@celestia-island/hikari 0.55.292 → 0.55.294
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 +1 -1
- package/src/animation/registerAnimations.test.ts +1 -0
- package/src/animation/registerAnimations.ts +5 -0
- package/src/components/HkRangeRibbon.barrel.test.ts +46 -0
- package/src/components/HkRangeRibbon.scss +401 -0
- package/src/components/HkRangeRibbon.test.tsx +1335 -0
- package/src/components/HkRangeRibbon.tsx +736 -0
- package/src/components/HkScrollPin.scrollcontract.test.ts +77 -1
- package/src/components/HkScrollPin.scss +24 -4
- package/src/components/HkSolarLocationSetting.scss +22 -1
- package/src/components/HkSolarLocationSetting.styles.test.ts +77 -0
- package/src/components/HkSolarLocationSetting.test.tsx +44 -3
- package/src/components/HkSolarLocationSetting.tsx +17 -1
- package/src/components/HkStepFlow.scss +10 -4
- package/src/i18n/locales/ar/components.json +8 -1
- package/src/i18n/locales/de/components.json +8 -1
- package/src/i18n/locales/en/components.json +8 -1
- package/src/i18n/locales/es/components.json +8 -1
- package/src/i18n/locales/fr/components.json +8 -1
- package/src/i18n/locales/ja/components.json +8 -1
- package/src/i18n/locales/ko/components.json +8 -1
- package/src/i18n/locales/pt/components.json +8 -1
- package/src/i18n/locales/ru/components.json +8 -1
- package/src/i18n/locales/zh-Hans/components.json +8 -1
- package/src/i18n/locales/zh-Hant/components.json +8 -1
- package/src/index.ts +8 -0
package/package.json
CHANGED
|
@@ -62,6 +62,7 @@ describe("registerAnimations", () => {
|
|
|
62
62
|
expect(byName.get("hk-pwd-breathe")?.infinite).toBe(true);
|
|
63
63
|
expect(byName.get("s-nav-item-badge-pulse")?.infinite).toBe(true);
|
|
64
64
|
expect(byName.get("s-status-bar-reconnecting-breathe")?.infinite).toBe(true);
|
|
65
|
+
expect(byName.get("hk-range-ribbon-pulse")?.infinite).toBe(true);
|
|
65
66
|
expect(byName.get("s-auth-card-in")?.infinite).toBe(false);
|
|
66
67
|
expect(byName.get("hk-rolling-number-up")?.infinite).toBe(false);
|
|
67
68
|
expect(byName.get("hk-modal-breadcrumb-in")?.infinite).toBe(false);
|
|
@@ -138,3 +138,8 @@ registerCssAnimation("hk-persistent-toast-card-in");
|
|
|
138
138
|
// (opacity + 6px rise, plays once; the card unmounts at dwell end, so
|
|
139
139
|
// no leave keyframe).
|
|
140
140
|
registerCssAnimation("hk-screen-identify-in");
|
|
141
|
+
// components/HkRangeRibbon.scss — pending-segment breathe (loops forever):
|
|
142
|
+
// a queued / in-flight row keeps pulsing while it has not settled, so a
|
|
143
|
+
// brushed window still reads as live. Reduced motion keeps the dashed
|
|
144
|
+
// pending look and drops the loop (house pattern, see HkSkeleton).
|
|
145
|
+
registerCssAnimation("hk-range-ribbon-pulse", { infinite: true });
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
HkRangeRibbon,
|
|
5
|
+
type RibbonLane,
|
|
6
|
+
type RibbonSegment,
|
|
7
|
+
type RibbonStatus,
|
|
8
|
+
type RibbonTip,
|
|
9
|
+
type RibbonTone,
|
|
10
|
+
} from "../index";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Public barrel guard for HkRangeRibbon (R3 review: deleting the component's
|
|
14
|
+
* export block from src/index.ts kept the entire 4696-test suite green —
|
|
15
|
+
* every other test reaches the module through the direct "./HkRangeRibbon"
|
|
16
|
+
* path, so only an external consumer would have noticed). Same shape as
|
|
17
|
+
* HkCalendarHeatmap.barrel.test.ts.
|
|
18
|
+
*/
|
|
19
|
+
describe("the public barrel", () => {
|
|
20
|
+
it("re-exports the ribbon component under its public name", () => {
|
|
21
|
+
expect(HkRangeRibbon, "HkRangeRibbon must be re-exported").toBeTruthy();
|
|
22
|
+
expect(HkRangeRibbon.name).toBe("HkRangeRibbon");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("re-exports the ribbon's public types", () => {
|
|
26
|
+
// Type-only exports vanish at runtime, so each one is CONSUMED here: a
|
|
27
|
+
// dropped `type Ribbon*` line fails vue-tsc on this file instead of
|
|
28
|
+
// leaving the runtime suite green.
|
|
29
|
+
const lane: RibbonLane = { key: "cpu", label: "CPU" };
|
|
30
|
+
const tone: RibbonTone = "success";
|
|
31
|
+
const status: RibbonStatus = "pending";
|
|
32
|
+
const tip: RibbonTip = { title: "Boot", lines: ["12ms"] };
|
|
33
|
+
const segment: RibbonSegment = {
|
|
34
|
+
id: "boot",
|
|
35
|
+
lane: lane.key,
|
|
36
|
+
start: 0,
|
|
37
|
+
end: 0.2,
|
|
38
|
+
label: "boot",
|
|
39
|
+
tone,
|
|
40
|
+
status,
|
|
41
|
+
tip,
|
|
42
|
+
};
|
|
43
|
+
expect(segment).toMatchObject({ id: "boot", lane: "cpu", start: 0, end: 0.2 });
|
|
44
|
+
expect(segment.tip?.lines).toEqual(["12ms"]);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
@use "../styles/polyfill" as *;
|
|
2
|
+
|
|
3
|
+
/* HkRangeRibbon — a multi-lane sequence ribbon with a brushed window.
|
|
4
|
+
Anatomy: a thin OVERVIEW strip (the full domain, always), the window
|
|
5
|
+
highlight + outside scrims + two edge handles riding over it, then the
|
|
6
|
+
lane list whose segment bars show either the full domain or the window.
|
|
7
|
+
|
|
8
|
+
Geometry is part of the contract and pinned by HkRangeRibbon.test.tsx:
|
|
9
|
+
the strip is 32px tall, a handle is an 8px half-overlapping grip
|
|
10
|
+
(translateX(-50%) so `left: 20%` centers it on the window edge), a
|
|
11
|
+
clipped segment keeps a 2px minimum so edge slivers stay visible, and
|
|
12
|
+
the reset chip clears the edge handle (right: 4px = the handle's
|
|
13
|
+
half-width, so a handle at 0% / 100% is never covered).
|
|
14
|
+
|
|
15
|
+
Every colour/size comes from hikari theme tokens; the only motion is the
|
|
16
|
+
pending-segment breathe, registered in animation/registerAnimations.ts. */
|
|
17
|
+
|
|
18
|
+
.hk-range-ribbon {
|
|
19
|
+
// ONE label column for both surfaces: the overview paints through an
|
|
20
|
+
// empty gutter of this width, so an x% bar sits at the same inline
|
|
21
|
+
// position in the strip and in its lane (the strip locates segments).
|
|
22
|
+
--hk-range-ribbon-label-col: minmax(48px, 84px);
|
|
23
|
+
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: var(--space-6);
|
|
27
|
+
width: 100%;
|
|
28
|
+
min-width: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── Overview strip (the full-domain minimap + brush surface) ─────────────
|
|
32
|
+
.hk-range-ribbon-overview {
|
|
33
|
+
position: relative;
|
|
34
|
+
display: grid;
|
|
35
|
+
grid-template-columns: var(--hk-range-ribbon-label-col) minmax(0, 1fr);
|
|
36
|
+
align-items: center;
|
|
37
|
+
gap: var(--space-4);
|
|
38
|
+
min-width: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The empty first cell that lines the strip up with the lane tracks.
|
|
42
|
+
.hk-range-ribbon-overview-gutter {
|
|
43
|
+
display: block;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The strip's own coordinate space: handles/reset are positioned against
|
|
48
|
+
// this box, which is exactly the strip's width (never the gutter too).
|
|
49
|
+
.hk-range-ribbon-overview-field {
|
|
50
|
+
position: relative;
|
|
51
|
+
min-width: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.hk-range-ribbon-strip {
|
|
55
|
+
position: relative;
|
|
56
|
+
display: block;
|
|
57
|
+
height: 32px;
|
|
58
|
+
margin: 0;
|
|
59
|
+
border-radius: var(--radius-sm);
|
|
60
|
+
background: rgb(var(--color-surface) / 55%);
|
|
61
|
+
box-shadow: inset 0 0 0 1px rgb(var(--color-border) / 60%);
|
|
62
|
+
cursor: crosshair;
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
touch-action: none;
|
|
65
|
+
@include user-select(none);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.hk-range-ribbon[data-window-enabled="false"] .hk-range-ribbon-strip {
|
|
69
|
+
cursor: default;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.hk-range-ribbon-strip:focus-visible {
|
|
73
|
+
outline: 2px solid rgb(var(--color-primary));
|
|
74
|
+
outline-offset: 2px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.hk-range-ribbon-overview-lanes {
|
|
78
|
+
position: absolute;
|
|
79
|
+
inset: 4px 0;
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
gap: 1px;
|
|
83
|
+
pointer-events: none;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.hk-range-ribbon-overview-row {
|
|
87
|
+
position: relative;
|
|
88
|
+
flex: 1 1 0;
|
|
89
|
+
min-height: 1px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.hk-range-ribbon-overview-segment {
|
|
93
|
+
position: absolute;
|
|
94
|
+
top: 0;
|
|
95
|
+
bottom: 0;
|
|
96
|
+
min-width: 1px;
|
|
97
|
+
border-radius: 1px;
|
|
98
|
+
background: rgb(var(--color-muted) / 45%);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.hk-range-ribbon-overview-segment[data-tone="success"] {
|
|
102
|
+
background: rgb(var(--color-success) / 60%);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.hk-range-ribbon-overview-segment[data-tone="secondary"] {
|
|
106
|
+
background: rgb(var(--color-secondary) / 55%);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.hk-range-ribbon-overview-segment[data-tone="warning"] {
|
|
110
|
+
background: rgb(var(--color-warning) / 70%);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.hk-range-ribbon-overview-segment[data-tone="error"] {
|
|
114
|
+
background: rgb(var(--color-error) / 65%);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.hk-range-ribbon-overview-segment[data-tone="muted"] {
|
|
118
|
+
background: rgb(var(--color-muted) / 25%);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.hk-range-ribbon-overview-segment[data-status="error"] {
|
|
122
|
+
background: rgb(var(--color-error) / 85%);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.hk-range-ribbon-overview-segment[data-status="pending"] {
|
|
126
|
+
opacity: 0.5;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Outside-the-window dim: two scrims rather than one masked sheet, so the
|
|
130
|
+
// highlighted window reads as a hole in the overview (and stays testable).
|
|
131
|
+
.hk-range-ribbon-scrim {
|
|
132
|
+
position: absolute;
|
|
133
|
+
top: 0;
|
|
134
|
+
bottom: 0;
|
|
135
|
+
background: rgb(var(--color-background) / 62%);
|
|
136
|
+
pointer-events: none;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.hk-range-ribbon-window {
|
|
140
|
+
position: absolute;
|
|
141
|
+
top: 0;
|
|
142
|
+
bottom: 0;
|
|
143
|
+
border: 1px solid rgb(var(--color-primary));
|
|
144
|
+
border-radius: 2px;
|
|
145
|
+
background: rgb(var(--color-primary) / 10%);
|
|
146
|
+
pointer-events: none;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.hk-range-ribbon-handle {
|
|
150
|
+
position: absolute;
|
|
151
|
+
top: 3px;
|
|
152
|
+
bottom: 3px;
|
|
153
|
+
z-index: 2;
|
|
154
|
+
width: 8px;
|
|
155
|
+
transform: translateX(-50%);
|
|
156
|
+
border: 1px solid rgb(var(--color-surface));
|
|
157
|
+
border-radius: var(--radius-full);
|
|
158
|
+
background: rgb(var(--color-primary));
|
|
159
|
+
cursor: ew-resize;
|
|
160
|
+
touch-action: none;
|
|
161
|
+
// Dragging the grip must not start a native text selection (house
|
|
162
|
+
// pattern: HkMediaSlider's track).
|
|
163
|
+
@include user-select(none);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.hk-range-ribbon-handle:hover {
|
|
167
|
+
box-shadow: 0 0 0 2px rgb(var(--color-primary) / 25%);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.hk-range-ribbon-handle:focus-visible {
|
|
171
|
+
outline: 2px solid rgb(var(--color-primary));
|
|
172
|
+
outline-offset: 2px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.hk-range-ribbon-reset {
|
|
176
|
+
position: absolute;
|
|
177
|
+
top: 4px;
|
|
178
|
+
right: 4px;
|
|
179
|
+
z-index: 3;
|
|
180
|
+
display: inline-flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
justify-content: center;
|
|
183
|
+
width: 18px;
|
|
184
|
+
height: 18px;
|
|
185
|
+
padding: 0;
|
|
186
|
+
border: 1px solid rgb(var(--color-border));
|
|
187
|
+
border-radius: var(--radius-sm);
|
|
188
|
+
background: rgb(var(--color-surface) / 85%);
|
|
189
|
+
color: rgb(var(--color-text));
|
|
190
|
+
cursor: pointer;
|
|
191
|
+
transition: background-color var(--duration-fast, 0.15s) ease;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.hk-range-ribbon-reset:hover {
|
|
195
|
+
background: rgb(var(--color-surface));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.hk-range-ribbon-reset:focus-visible {
|
|
199
|
+
outline: 2px solid rgb(var(--color-primary));
|
|
200
|
+
outline-offset: 1px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Visually hidden, still read by AT: labels the keyboard contract the
|
|
204
|
+
// strip's slider role cannot express by itself.
|
|
205
|
+
.hk-range-ribbon-hint {
|
|
206
|
+
position: absolute;
|
|
207
|
+
width: 1px;
|
|
208
|
+
height: 1px;
|
|
209
|
+
margin: -1px;
|
|
210
|
+
padding: 0;
|
|
211
|
+
border: 0;
|
|
212
|
+
overflow: hidden;
|
|
213
|
+
clip: rect(0 0 0 0);
|
|
214
|
+
white-space: nowrap;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Lane list ───────────────────────────────────────────────────────────
|
|
218
|
+
.hk-range-ribbon-lanes {
|
|
219
|
+
display: flex;
|
|
220
|
+
flex-direction: column;
|
|
221
|
+
gap: var(--space-2);
|
|
222
|
+
min-width: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.hk-range-ribbon-lane {
|
|
226
|
+
display: grid;
|
|
227
|
+
grid-template-columns: var(--hk-range-ribbon-label-col) minmax(0, 1fr);
|
|
228
|
+
align-items: center;
|
|
229
|
+
gap: var(--space-4);
|
|
230
|
+
min-width: 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.hk-range-ribbon-lane-label {
|
|
234
|
+
overflow: hidden;
|
|
235
|
+
color: rgb(var(--color-muted));
|
|
236
|
+
font-size: var(--text-2xs);
|
|
237
|
+
// 1.3 floor: an overflow clip box shorter than the font ink box cuts the
|
|
238
|
+
// descenders of g/j/p/q/y (textClip.contract.test.ts).
|
|
239
|
+
line-height: 1.3;
|
|
240
|
+
text-overflow: ellipsis;
|
|
241
|
+
white-space: nowrap;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.hk-range-ribbon-lane-track {
|
|
245
|
+
position: relative;
|
|
246
|
+
height: 20px;
|
|
247
|
+
border-radius: var(--radius-sm);
|
|
248
|
+
background: rgb(var(--color-muted) / 8%);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ── Segment bars ────────────────────────────────────────────────────────
|
|
252
|
+
// No overflow:hidden here: the tip card is a child and must float above
|
|
253
|
+
// the neighbouring lanes. The label clips itself instead.
|
|
254
|
+
.hk-range-ribbon-segment {
|
|
255
|
+
position: absolute;
|
|
256
|
+
top: 3px;
|
|
257
|
+
bottom: 3px;
|
|
258
|
+
display: flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
box-sizing: border-box;
|
|
261
|
+
min-width: 2px;
|
|
262
|
+
padding: 0 var(--space-2);
|
|
263
|
+
border: 1px solid rgb(var(--color-border) / 45%);
|
|
264
|
+
border-radius: var(--radius-sm);
|
|
265
|
+
background: rgb(var(--color-muted) / 30%);
|
|
266
|
+
color: rgb(var(--color-text));
|
|
267
|
+
font-size: var(--text-2xs);
|
|
268
|
+
line-height: 1.3;
|
|
269
|
+
cursor: pointer;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.hk-range-ribbon-segment[data-tone="success"] {
|
|
273
|
+
border-color: rgb(var(--color-success) / 55%);
|
|
274
|
+
background: rgb(var(--color-success) / 22%);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.hk-range-ribbon-segment[data-tone="secondary"] {
|
|
278
|
+
border-color: rgb(var(--color-secondary) / 55%);
|
|
279
|
+
background: rgb(var(--color-secondary) / 20%);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.hk-range-ribbon-segment[data-tone="warning"] {
|
|
283
|
+
border-color: rgb(var(--color-warning) / 65%);
|
|
284
|
+
background: rgb(var(--color-warning) / 24%);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.hk-range-ribbon-segment[data-tone="error"] {
|
|
288
|
+
border-color: rgb(var(--color-error) / 60%);
|
|
289
|
+
background: rgb(var(--color-error) / 22%);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.hk-range-ribbon-segment[data-tone="muted"] {
|
|
293
|
+
border-color: rgb(var(--color-muted) / 35%);
|
|
294
|
+
background: rgb(var(--color-muted) / 16%);
|
|
295
|
+
color: rgb(var(--color-muted));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Status channels win over the tone channel: an errored row is an error
|
|
299
|
+
// regardless of what tone it also carries.
|
|
300
|
+
.hk-range-ribbon-segment[data-status="error"] {
|
|
301
|
+
border-color: rgb(var(--color-error));
|
|
302
|
+
background: rgb(var(--color-error) / 32%);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Queued / in-flight rows still breathe — the one motion in the ribbon.
|
|
306
|
+
.hk-range-ribbon-segment[data-status="pending"] {
|
|
307
|
+
border-style: dashed;
|
|
308
|
+
animation: hk-range-ribbon-pulse 1.6s ease-in-out infinite;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@keyframes hk-range-ribbon-pulse {
|
|
312
|
+
0%,
|
|
313
|
+
100% {
|
|
314
|
+
opacity: 1;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
50% {
|
|
318
|
+
opacity: 0.5;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// House pattern (HkSkeleton / HkStatusPill): keep the dashed pending look,
|
|
323
|
+
// drop the loop.
|
|
324
|
+
@media (prefers-reduced-motion: reduce) {
|
|
325
|
+
.hk-range-ribbon-segment[data-status="pending"] {
|
|
326
|
+
animation: none;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.hk-range-ribbon-segment[data-selected="true"] {
|
|
331
|
+
border-color: rgb(var(--color-primary));
|
|
332
|
+
box-shadow: 0 0 0 1px rgb(var(--color-primary));
|
|
333
|
+
z-index: 1;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.hk-range-ribbon-segment:hover {
|
|
337
|
+
border-color: rgb(var(--color-primary) / 70%);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.hk-range-ribbon-segment:focus-visible {
|
|
341
|
+
z-index: 2;
|
|
342
|
+
border-color: rgb(var(--color-primary));
|
|
343
|
+
outline: 2px solid rgb(var(--color-primary));
|
|
344
|
+
outline-offset: 1px;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.hk-range-ribbon-segment-label {
|
|
348
|
+
display: block;
|
|
349
|
+
width: 100%;
|
|
350
|
+
overflow: hidden;
|
|
351
|
+
// Same descender floor as the lane label: this is the clip box.
|
|
352
|
+
line-height: 1.3;
|
|
353
|
+
text-overflow: ellipsis;
|
|
354
|
+
white-space: nowrap;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ── Tip card ────────────────────────────────────────────────────────────
|
|
358
|
+
.hk-range-ribbon-tip {
|
|
359
|
+
position: absolute;
|
|
360
|
+
bottom: calc(100% + var(--space-6));
|
|
361
|
+
left: 50%;
|
|
362
|
+
z-index: 4;
|
|
363
|
+
display: flex;
|
|
364
|
+
flex-direction: column;
|
|
365
|
+
gap: 1px;
|
|
366
|
+
min-width: 108px;
|
|
367
|
+
max-width: 240px;
|
|
368
|
+
padding: var(--space-4) var(--space-6);
|
|
369
|
+
transform: translateX(-50%);
|
|
370
|
+
border: 1px solid rgb(var(--color-border));
|
|
371
|
+
border-radius: var(--radius-md);
|
|
372
|
+
background: rgb(var(--color-surface));
|
|
373
|
+
box-shadow: 0 6px 18px rgb(var(--color-text) / 16%);
|
|
374
|
+
color: rgb(var(--color-text));
|
|
375
|
+
font-size: var(--text-2xs);
|
|
376
|
+
line-height: 1.35;
|
|
377
|
+
text-align: start;
|
|
378
|
+
white-space: normal;
|
|
379
|
+
pointer-events: none;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// A tip on a segment near either end of the track grows INWARD from that
|
|
383
|
+
// segment's own edge instead of centring (and overflowing) the ribbon.
|
|
384
|
+
.hk-range-ribbon-tip[data-anchor="start"] {
|
|
385
|
+
left: 0;
|
|
386
|
+
transform: none;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.hk-range-ribbon-tip[data-anchor="end"] {
|
|
390
|
+
left: auto;
|
|
391
|
+
right: 0;
|
|
392
|
+
transform: none;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.hk-range-ribbon-tip-title {
|
|
396
|
+
font-weight: 600;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.hk-range-ribbon-tip-line {
|
|
400
|
+
color: rgb(var(--color-muted));
|
|
401
|
+
}
|