@roy-ui/ui 0.0.9 → 0.0.11

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 CHANGED
@@ -9,7 +9,7 @@
9
9
  [![types](https://img.shields.io/npm/types/@roy-ui/ui?color=3178C6&logo=typescript&logoColor=white)](https://www.npmjs.com/package/@roy-ui/ui)
10
10
  [![license](https://img.shields.io/npm/l/@roy-ui/ui?color=22c55e)](https://github.com/DibbayajyotiRoy/RoyUI/blob/main/LICENSE)
11
11
 
12
- **[Open the live documentation →](https://royui.dev/components/data-table)**
12
+ **[Open the live documentation →](https://roy-ui-docs.vercel.app/components/data-table)**
13
13
 
14
14
  <a href="https://github.com/DibbayajyotiRoy/RoyUI/blob/main/apps/docs/lib/demo/linkedin2.mp4">
15
15
  <img
@@ -114,7 +114,7 @@ export default function OrdersTable({ orders }: { orders: Order[] }) {
114
114
 
115
115
  | Component | What it does |
116
116
  | --- | --- |
117
- | **[`DataTable<T>`](https://royui.dev/components/data-table)** | Generic, fully-featured **React data table**. Search across columns, sort, paginate, **drag headers to reorder**, **drag the right edge to resize**, **Columns menu** to hide & restore, **CSV / JSON export & import**, **per-zone typography** for headers / row-headers / cells, optional `fitColumns` to disable horizontal scroll, optional `localStorage` persistence. |
117
+ | **[`DataTable<T>`](https://roy-ui-docs.vercel.app/components/data-table)** | Generic, fully-featured **React data table**. Search across columns, sort, paginate, **drag headers to reorder**, **drag the right edge to resize**, **Columns menu** to hide & restore, **CSV / JSON export & import**, **per-zone typography** for headers / row-headers / cells, optional `fitColumns` to disable horizontal scroll, optional `localStorage` persistence. |
118
118
  | **`Table`** + parts | Standalone primitive — `Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`. Scrollable rows (`visibleRows`, default 7), sticky header, density scale (compact / cozy / comfortable), inline `Spinner`. |
119
119
  | **`TableSearch`** | Debounced search input, clear button, controlled & uncontrolled modes. |
120
120
  | **`Pagination`** | Numbered pages with text Prev / Next, ellipsis, optional `Page X of Y` summary. |
@@ -325,7 +325,7 @@ Yes. `dataIO.export.enabled` adds an Export button; `dataIO.import.enabled` adds
325
325
 
326
326
  ## Links
327
327
 
328
- - **Live documentation:** <https://royui.dev>
328
+ - **Live documentation:** <https://roy-ui-docs.vercel.app>
329
329
  - **Source code:** <https://github.com/DibbayajyotiRoy/RoyUI>
330
330
  - **Issues:** <https://github.com/DibbayajyotiRoy/RoyUI/issues>
331
331
  - **Releases:** <https://github.com/DibbayajyotiRoy/RoyUI/releases>
@@ -0,0 +1,270 @@
1
+ .royui-card {
2
+ /* Surfaces and ink — every one is a variable. The default is a premium
3
+ off-white (not pure white), which reads warmer and reads on a white page.
4
+ Opt into dark with theme="dark" (.royui-card--dark) or theme="auto". */
5
+ --royui-card-bg: #fafaf8;
6
+ --royui-card-fg: #1a1a1c;
7
+ --royui-card-muted: rgba(0, 0, 0, 0.52);
8
+ --royui-card-faint: rgba(0, 0, 0, 0.42);
9
+ --royui-card-line: rgba(0, 0, 0, 0.08);
10
+ --royui-card-radius: 24px;
11
+ /* A thin frame around the photo. Inner radius = radius − pad keeps the
12
+ corners concentric (24 − 8 = 16, the gallery's own radius). */
13
+ --royui-card-pad: 8px;
14
+ --royui-card-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
15
+ /* The price reads as premium when it's large and light, not bold. */
16
+ --royui-card-price-weight: 500;
17
+ /* Selection stays legible on the surface — a soft tint, not the browser blue. */
18
+ --royui-card-selection-bg: rgba(20, 20, 22, 0.12);
19
+ --royui-card-selection-fg: #1a1a1c;
20
+
21
+ display: flex;
22
+ flex-direction: column;
23
+ box-sizing: border-box;
24
+ width: 100%;
25
+ max-width: 360px;
26
+ /* The card is its own query container, so type scales to the card's width
27
+ (cqi units below) rather than the viewport — it fits in a sidebar, a
28
+ grid cell, or full-bleed without a media query. */
29
+ container-type: inline-size;
30
+ padding: var(--royui-card-pad);
31
+ background: var(--royui-card-bg);
32
+ color: var(--royui-card-fg);
33
+ border-radius: var(--royui-card-radius);
34
+ font-family: inherit;
35
+ /* A ring, a contact shadow, and a soft cast — the card rests, it doesn't
36
+ float. The ring is firm enough to hold the edge on a white background. */
37
+ box-shadow:
38
+ 0 0 0 1px rgba(0, 0, 0, 0.07),
39
+ 0 1px 2px rgba(0, 0, 0, 0.04),
40
+ 0 14px 34px -16px rgba(0, 0, 0, 0.22);
41
+ transition:
42
+ transform 300ms var(--royui-card-ease),
43
+ box-shadow 300ms var(--royui-card-ease);
44
+ }
45
+
46
+ .royui-card--interactive:hover {
47
+ transform: translateY(-2px);
48
+ box-shadow:
49
+ 0 0 0 1px rgba(0, 0, 0, 0.05),
50
+ 0 2px 4px rgba(0, 0, 0, 0.05),
51
+ 0 22px 48px -18px rgba(0, 0, 0, 0.28);
52
+ }
53
+ /* The photo eases in a hair as the whole card lifts — barely there. */
54
+ .royui-card--interactive:hover .royui-carousel__img {
55
+ transform: scale(1.045);
56
+ }
57
+
58
+ /* Force a legible selection — the browser default can wash text out on the
59
+ off-white surface. */
60
+ .royui-card ::selection {
61
+ background: var(--royui-card-selection-bg);
62
+ color: var(--royui-card-selection-fg);
63
+ }
64
+
65
+ /* ── Badge ───────────────────────────────────────────────────────────── */
66
+ .royui-card__badge {
67
+ position: absolute;
68
+ top: 11px;
69
+ left: 11px;
70
+ display: inline-flex;
71
+ align-items: center;
72
+ gap: 5px;
73
+ padding: 4px 9px 4px 7px;
74
+ background: rgba(255, 255, 255, 0.9);
75
+ -webkit-backdrop-filter: blur(10px) saturate(1.2);
76
+ backdrop-filter: blur(10px) saturate(1.2);
77
+ border-radius: 99px;
78
+ color: #1a1a1c;
79
+ font-size: 12px;
80
+ font-size: clamp(11px, 3.7cqi, 12px);
81
+ font-weight: 400;
82
+ letter-spacing: 0;
83
+ line-height: 1;
84
+ box-shadow:
85
+ 0 0 0 0.5px rgba(0, 0, 0, 0.04),
86
+ 0 1px 2px rgba(0, 0, 0, 0.1),
87
+ 0 3px 10px -3px rgba(0, 0, 0, 0.22);
88
+ }
89
+ .royui-card__badge-icon {
90
+ display: inline-flex;
91
+ color: #f5b400;
92
+ }
93
+
94
+ /* ── Body ────────────────────────────────────────────────────────────── */
95
+ /* Horizontal padding insets the text a touch past the photo edge; the photo
96
+ and button sit at the card padding so they share one clean vertical line. */
97
+ .royui-card__body {
98
+ padding: 14px 6px 2px;
99
+ }
100
+
101
+ .royui-card__price-row {
102
+ display: flex;
103
+ align-items: baseline;
104
+ flex-wrap: wrap;
105
+ gap: 4px 8px;
106
+ min-width: 0;
107
+ }
108
+ .royui-card__price {
109
+ font-size: 23px;
110
+ font-size: clamp(18px, 7.2cqi, 23px);
111
+ font-weight: var(--royui-card-price-weight);
112
+ letter-spacing: -0.03em;
113
+ font-variant-numeric: tabular-nums;
114
+ }
115
+ .royui-card__price-label {
116
+ font-size: 14px;
117
+ font-size: clamp(12px, 4.4cqi, 14px);
118
+ font-weight: 450;
119
+ color: var(--royui-card-muted);
120
+ }
121
+
122
+ .royui-card__subtitle {
123
+ margin: 6px 0 0;
124
+ font-size: 14px;
125
+ font-size: clamp(12.5px, 4.4cqi, 14px);
126
+ line-height: 1.4;
127
+ color: var(--royui-card-muted);
128
+ }
129
+
130
+ .royui-card__divider {
131
+ height: 1px;
132
+ margin: 14px 0;
133
+ background: var(--royui-card-line);
134
+ }
135
+
136
+ /* Stats stay on one row and sit flush to both content edges, so the left and
137
+ right margins from the card border match. The hairline divider rides the
138
+ middle. */
139
+ .royui-card__stats {
140
+ display: flex;
141
+ flex-wrap: nowrap;
142
+ align-items: center;
143
+ justify-content: space-between;
144
+ gap: 10px;
145
+ gap: clamp(8px, 3.2cqi, 10px);
146
+ font-size: 14px;
147
+ font-size: clamp(12px, 4.4cqi, 14px);
148
+ color: var(--royui-card-fg);
149
+ }
150
+ .royui-card__stat {
151
+ display: inline-flex;
152
+ align-items: center;
153
+ gap: 6px;
154
+ flex: none;
155
+ white-space: nowrap;
156
+ }
157
+ .royui-card__stat-sep {
158
+ flex: none;
159
+ width: 1px;
160
+ height: 15px;
161
+ background: var(--royui-card-line);
162
+ }
163
+ .royui-card__stat-icon {
164
+ display: inline-flex;
165
+ flex: none;
166
+ color: var(--royui-card-faint);
167
+ }
168
+ /* The figure carries weight; the descriptor is dimmed, like the reference. */
169
+ .royui-card__stat-value {
170
+ color: var(--royui-card-fg);
171
+ }
172
+ .royui-card__stat-label {
173
+ color: var(--royui-card-muted);
174
+ }
175
+
176
+ .royui-card__footer {
177
+ display: flex;
178
+ align-items: center;
179
+ justify-content: space-between;
180
+ flex-wrap: wrap;
181
+ gap: 6px 12px;
182
+ margin-top: 14px;
183
+ font-size: 14px;
184
+ font-size: clamp(12px, 4.4cqi, 14px);
185
+ }
186
+ .royui-card__author {
187
+ color: var(--royui-card-muted);
188
+ min-width: 0;
189
+ }
190
+ .royui-card__author-link {
191
+ color: var(--royui-card-fg);
192
+ font-weight: 550;
193
+ text-decoration: underline;
194
+ text-underline-offset: 2px;
195
+ text-decoration-thickness: 1px;
196
+ text-decoration-color: var(--royui-card-line);
197
+ transition: text-decoration-color 180ms var(--royui-card-ease);
198
+ }
199
+ a.royui-card__author-link:hover {
200
+ text-decoration-color: currentColor;
201
+ }
202
+ .royui-card__meta {
203
+ color: var(--royui-card-faint);
204
+ white-space: nowrap;
205
+ }
206
+
207
+ .royui-card__action {
208
+ margin-top: 16px;
209
+ }
210
+
211
+ /* ── Dark theme ──────────────────────────────────────────────────────────
212
+ Opt in with theme="dark" (.royui-card--dark). theme="auto" (.royui-card--auto)
213
+ applies the same tokens only when the OS asks for dark — so the default light
214
+ card is never silently swapped out from under a dark page. */
215
+ .royui-card--dark {
216
+ --royui-card-bg: #161617;
217
+ --royui-card-fg: #f5f5f7;
218
+ --royui-card-muted: rgba(255, 255, 255, 0.55);
219
+ --royui-card-faint: rgba(255, 255, 255, 0.4);
220
+ --royui-card-line: rgba(255, 255, 255, 0.1);
221
+ --royui-card-selection-bg: rgba(245, 245, 247, 0.2);
222
+ --royui-card-selection-fg: #f5f5f7;
223
+ box-shadow:
224
+ 0 0 0 1px rgba(255, 255, 255, 0.1),
225
+ 0 14px 34px -16px rgba(0, 0, 0, 0.6);
226
+ }
227
+ .royui-card--dark.royui-card--interactive:hover {
228
+ box-shadow:
229
+ 0 0 0 1px rgba(255, 255, 255, 0.12),
230
+ 0 22px 48px -18px rgba(0, 0, 0, 0.7);
231
+ }
232
+ .royui-card--dark .royui-card__badge {
233
+ background: rgba(28, 28, 30, 0.82);
234
+ color: #f5f5f7;
235
+ }
236
+
237
+ @media (prefers-color-scheme: dark) {
238
+ .royui-card--auto {
239
+ --royui-card-bg: #161617;
240
+ --royui-card-fg: #f5f5f7;
241
+ --royui-card-muted: rgba(255, 255, 255, 0.55);
242
+ --royui-card-faint: rgba(255, 255, 255, 0.4);
243
+ --royui-card-line: rgba(255, 255, 255, 0.1);
244
+ --royui-card-selection-bg: rgba(245, 245, 247, 0.2);
245
+ --royui-card-selection-fg: #f5f5f7;
246
+ box-shadow:
247
+ 0 0 0 1px rgba(255, 255, 255, 0.1),
248
+ 0 14px 34px -16px rgba(0, 0, 0, 0.6);
249
+ }
250
+ .royui-card--auto.royui-card--interactive:hover {
251
+ box-shadow:
252
+ 0 0 0 1px rgba(255, 255, 255, 0.12),
253
+ 0 22px 48px -18px rgba(0, 0, 0, 0.7);
254
+ }
255
+ .royui-card--auto .royui-card__badge {
256
+ background: rgba(28, 28, 30, 0.82);
257
+ color: #f5f5f7;
258
+ }
259
+ }
260
+
261
+ @media (prefers-reduced-motion: reduce) {
262
+ .royui-card,
263
+ .royui-card--interactive:hover {
264
+ transition: none;
265
+ transform: none;
266
+ }
267
+ .royui-card--interactive:hover .royui-carousel__img {
268
+ transform: none;
269
+ }
270
+ }
@@ -0,0 +1,148 @@
1
+ .royui-carousel {
2
+ /* Tunables — radius and ratio are the ones you'll reach for most. */
3
+ --royui-carousel-ratio: 4 / 3;
4
+ --royui-carousel-radius: 16px;
5
+ --royui-carousel-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
6
+ --royui-carousel-dot: rgba(255, 255, 255, 0.55);
7
+ --royui-carousel-dot-active: #ffffff;
8
+
9
+ position: relative;
10
+ overflow: hidden;
11
+ border-radius: var(--royui-carousel-radius);
12
+ aspect-ratio: var(--royui-carousel-ratio);
13
+ background: rgba(0, 0, 0, 0.06);
14
+ }
15
+
16
+ .royui-carousel__viewport {
17
+ width: 100%;
18
+ height: 100%;
19
+ /* Let the browser own vertical scroll; we only claim the horizontal axis. */
20
+ touch-action: pan-y;
21
+ cursor: grab;
22
+ -webkit-tap-highlight-color: transparent;
23
+ }
24
+ .royui-carousel__viewport:active {
25
+ cursor: grabbing;
26
+ }
27
+ .royui-carousel__viewport:focus-visible {
28
+ outline: 2px solid rgba(255, 255, 255, 0.9);
29
+ outline-offset: -2px;
30
+ }
31
+
32
+ .royui-carousel__track {
33
+ display: flex;
34
+ width: 100%;
35
+ height: 100%;
36
+ /* The signature slide — long, eased, with a touch of overshoot at the tail. */
37
+ transition: transform 520ms var(--royui-carousel-ease);
38
+ will-change: transform;
39
+ }
40
+ /* While the finger is down the track tracks the pointer 1:1, no easing. */
41
+ .royui-carousel__track--dragging {
42
+ transition: none;
43
+ }
44
+
45
+ .royui-carousel__slide {
46
+ position: relative;
47
+ flex: 0 0 100%;
48
+ width: 100%;
49
+ height: 100%;
50
+ overflow: hidden;
51
+ }
52
+
53
+ .royui-carousel__img {
54
+ display: block;
55
+ width: 100%;
56
+ height: 100%;
57
+ object-fit: cover;
58
+ pointer-events: none;
59
+ user-select: none;
60
+ -webkit-user-drag: none;
61
+ /* Hover-zoom is driven by the card; the transition lives here. */
62
+ transition: transform 700ms var(--royui-carousel-ease);
63
+ }
64
+
65
+ /* A faint floor of shade so white dots stay legible over any photo. */
66
+ .royui-carousel__scrim {
67
+ position: absolute;
68
+ inset: auto 0 0 0;
69
+ height: 38%;
70
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.3), transparent);
71
+ opacity: 0;
72
+ transition: opacity 320ms var(--royui-carousel-ease);
73
+ pointer-events: none;
74
+ }
75
+ .royui-carousel--has-dots .royui-carousel__scrim {
76
+ opacity: 1;
77
+ }
78
+
79
+ .royui-carousel__overlay {
80
+ position: absolute;
81
+ inset: 0;
82
+ z-index: 3;
83
+ pointer-events: none;
84
+ }
85
+ /* Re-enable interaction for whatever the overlay actually contains. */
86
+ .royui-carousel__overlay > * {
87
+ pointer-events: auto;
88
+ }
89
+
90
+ .royui-carousel__dots {
91
+ position: absolute;
92
+ inset: auto 0 12px 0;
93
+ z-index: 4;
94
+ display: flex;
95
+ align-items: center;
96
+ justify-content: center;
97
+ gap: 6px;
98
+ }
99
+
100
+ .royui-carousel__dot {
101
+ width: 6px;
102
+ height: 6px;
103
+ padding: 0;
104
+ border: none;
105
+ border-radius: 99px;
106
+ background: var(--royui-carousel-dot);
107
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.28);
108
+ cursor: pointer;
109
+ -webkit-tap-highlight-color: transparent;
110
+ /* The active dot doesn't pop — it stretches into a pill. */
111
+ transition:
112
+ width 380ms var(--royui-carousel-ease),
113
+ background 380ms var(--royui-carousel-ease),
114
+ opacity 380ms var(--royui-carousel-ease);
115
+ }
116
+ .royui-carousel__dot:hover {
117
+ background: rgba(255, 255, 255, 0.85);
118
+ }
119
+ .royui-carousel__dot--active {
120
+ width: 20px;
121
+ background: var(--royui-carousel-dot-active);
122
+ }
123
+ .royui-carousel__dot:focus-visible {
124
+ outline: 2px solid #fff;
125
+ outline-offset: 2px;
126
+ }
127
+
128
+ /* Screen-reader-only live region. */
129
+ .royui-carousel__status {
130
+ position: absolute;
131
+ width: 1px;
132
+ height: 1px;
133
+ margin: -1px;
134
+ padding: 0;
135
+ overflow: hidden;
136
+ clip: rect(0 0 0 0);
137
+ white-space: nowrap;
138
+ border: 0;
139
+ }
140
+
141
+ @media (prefers-reduced-motion: reduce) {
142
+ .royui-carousel__track,
143
+ .royui-carousel__img,
144
+ .royui-carousel__dot,
145
+ .royui-carousel__scrim {
146
+ transition: none;
147
+ }
148
+ }
@@ -15,7 +15,9 @@
15
15
  --royui-tp-hand-quiet: rgba(20, 20, 20, 0.4);
16
16
  --royui-tp-hand-active: rgb(20, 20, 20);
17
17
  --royui-tp-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
18
+ --royui-tp-spring: cubic-bezier(0.34, 1.4, 0.5, 1);
18
19
  --royui-tp-dur: 160ms;
20
+ --royui-tp-dur-lg: 320ms;
19
21
 
20
22
  position: relative;
21
23
  display: inline-block;
@@ -97,12 +99,31 @@
97
99
  }
98
100
 
99
101
  .royui-tp__variants {
102
+ position: relative;
100
103
  display: inline-flex;
101
104
  background: var(--royui-tp-hover-bg);
102
105
  border-radius: 7px;
103
106
  padding: 2px;
104
107
  }
108
+ /* Sliding pill — tracks the active variant. */
109
+ .royui-tp__variant-thumb {
110
+ position: absolute;
111
+ top: 2px;
112
+ bottom: 2px;
113
+ left: 2px;
114
+ width: calc(50% - 2px);
115
+ background: var(--royui-tp-bg);
116
+ border-radius: 5px;
117
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
118
+ transition: transform var(--royui-tp-dur-lg) var(--royui-tp-spring);
119
+ pointer-events: none;
120
+ }
121
+ .royui-tp__variants[data-active='digital'] .royui-tp__variant-thumb {
122
+ transform: translateX(100%);
123
+ }
105
124
  .royui-tp__variant {
125
+ position: relative;
126
+ z-index: 1;
106
127
  background: transparent;
107
128
  border: 0;
108
129
  padding: 4px 10px;
@@ -111,17 +132,13 @@
111
132
  color: var(--royui-tp-muted);
112
133
  cursor: pointer;
113
134
  border-radius: 5px;
114
- transition:
115
- background var(--royui-tp-dur) var(--royui-tp-ease),
116
- color var(--royui-tp-dur) var(--royui-tp-ease);
135
+ transition: color var(--royui-tp-dur) var(--royui-tp-ease);
117
136
  }
118
137
  .royui-tp__variant:hover {
119
138
  color: var(--royui-tp-fg);
120
139
  }
121
140
  .royui-tp__variant--on {
122
- background: var(--royui-tp-bg);
123
141
  color: var(--royui-tp-fg);
124
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
125
142
  }
126
143
 
127
144
  .royui-tp__body {
@@ -130,6 +147,38 @@
130
147
  padding: 4px 0 8px;
131
148
  }
132
149
 
150
+ /* ── Analog ↔ Digital crossfade ─────────────────────────── */
151
+
152
+ .royui-tp__switch {
153
+ position: relative;
154
+ width: 100%;
155
+ transition: height var(--royui-tp-dur-lg) var(--royui-tp-ease);
156
+ overflow: hidden;
157
+ }
158
+ .royui-tp__layer {
159
+ position: absolute;
160
+ top: 0;
161
+ left: 0;
162
+ right: 0;
163
+ display: flex;
164
+ justify-content: center;
165
+ opacity: 0;
166
+ transform: scale(0.96);
167
+ filter: blur(4px);
168
+ pointer-events: none;
169
+ transition:
170
+ opacity var(--royui-tp-dur-lg) var(--royui-tp-ease),
171
+ transform var(--royui-tp-dur-lg) var(--royui-tp-ease),
172
+ filter var(--royui-tp-dur-lg) var(--royui-tp-ease);
173
+ }
174
+ .royui-tp__layer--active {
175
+ position: relative;
176
+ opacity: 1;
177
+ transform: none;
178
+ filter: none;
179
+ pointer-events: auto;
180
+ }
181
+
133
182
  /* ── Analog ─────────────────────────────────────────────── */
134
183
 
135
184
  .royui-tp-analog {
@@ -373,6 +422,74 @@
373
422
  border: 1px solid var(--royui-tp-accent);
374
423
  }
375
424
 
425
+ /* ── Range — Start / End legs ───────────────────────────── */
426
+
427
+ .royui-trp__legs {
428
+ position: relative;
429
+ display: grid;
430
+ grid-template-columns: 1fr 1fr;
431
+ gap: 2px;
432
+ background: var(--royui-tp-hover-bg);
433
+ border-radius: 9px;
434
+ padding: 3px;
435
+ margin-bottom: 12px;
436
+ }
437
+ .royui-trp__leg-thumb {
438
+ position: absolute;
439
+ top: 3px;
440
+ bottom: 3px;
441
+ left: 3px;
442
+ width: calc(50% - 3px);
443
+ background: var(--royui-tp-bg);
444
+ border: 1px solid var(--royui-tp-border);
445
+ border-radius: 7px;
446
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.07);
447
+ transition: transform var(--royui-tp-dur-lg) var(--royui-tp-spring);
448
+ pointer-events: none;
449
+ }
450
+ .royui-trp__legs[data-active='to'] .royui-trp__leg-thumb {
451
+ transform: translateX(100%);
452
+ }
453
+ .royui-trp__leg {
454
+ position: relative;
455
+ z-index: 1;
456
+ display: flex;
457
+ flex-direction: column;
458
+ align-items: flex-start;
459
+ gap: 1px;
460
+ background: transparent;
461
+ border: 0;
462
+ padding: 6px 11px;
463
+ border-radius: 7px;
464
+ cursor: pointer;
465
+ font: inherit;
466
+ }
467
+ .royui-trp__leg-label {
468
+ font-size: 10.5px;
469
+ font-weight: 500;
470
+ letter-spacing: 0.03em;
471
+ text-transform: uppercase;
472
+ color: var(--royui-tp-subtle);
473
+ transition: color var(--royui-tp-dur) var(--royui-tp-ease);
474
+ }
475
+ .royui-trp__leg-time {
476
+ font-size: 15px;
477
+ font-weight: 500;
478
+ color: var(--royui-tp-muted);
479
+ font-variant-numeric: tabular-nums;
480
+ letter-spacing: -0.01em;
481
+ transition: color var(--royui-tp-dur) var(--royui-tp-ease);
482
+ }
483
+ .royui-trp__leg[aria-selected='true'] .royui-trp__leg-label {
484
+ color: var(--royui-tp-muted);
485
+ }
486
+ .royui-trp__leg[aria-selected='true'] .royui-trp__leg-time {
487
+ color: var(--royui-tp-fg);
488
+ }
489
+ .royui-trp__leg:hover .royui-trp__leg-time {
490
+ color: var(--royui-tp-fg);
491
+ }
492
+
376
493
  @keyframes royui-tp-in {
377
494
  from { opacity: 0; transform: translateY(-4px) scale(0.98); }
378
495
  to { opacity: 1; transform: none; }
@@ -408,4 +525,10 @@
408
525
 
409
526
  @media (prefers-reduced-motion: reduce) {
410
527
  .royui-tp__panel { animation: none; }
528
+ .royui-tp__switch,
529
+ .royui-tp__layer,
530
+ .royui-tp__variant-thumb,
531
+ .royui-trp__leg-thumb {
532
+ transition: none;
533
+ }
411
534
  }
@@ -36,4 +36,28 @@ interface TimePickerProps {
36
36
  declare function formatTime(t: TimeValue | null | undefined, hourCycle?: 12 | 24): string;
37
37
  declare function TimePicker({ value, defaultValue, onChange, variant, switchable, hourCycle, minuteStep, placeholder, align, className, style, triggerLabel, disabled, }: TimePickerProps): react_jsx_runtime.JSX.Element;
38
38
 
39
- export { AnalogClock as A, TimePicker as T, type AnalogClockProps as a, type TimePickerProps as b, type TimePickerVariant as c, type TimeValue as d, formatTime as f };
39
+ type TimeRangeValue = {
40
+ from: TimeValue | null;
41
+ to: TimeValue | null;
42
+ };
43
+ interface TimeRangePickerProps {
44
+ value?: TimeRangeValue | null;
45
+ defaultValue?: TimeRangeValue | null;
46
+ onChange?: (next: TimeRangeValue) => void;
47
+ /** Picker style. Default 'analog'. */
48
+ variant?: TimePickerVariant;
49
+ /** Allow the user to switch between variants. Default true. */
50
+ switchable?: boolean;
51
+ hourCycle?: 12 | 24;
52
+ minuteStep?: number;
53
+ placeholder?: string;
54
+ align?: 'left' | 'right';
55
+ className?: string;
56
+ style?: CSSProperties;
57
+ triggerLabel?: ReactNode;
58
+ disabled?: boolean;
59
+ }
60
+ declare function formatTimeRange(range: TimeRangeValue | null | undefined, hourCycle?: 12 | 24): string;
61
+ declare function TimeRangePicker({ value, defaultValue, onChange, variant, switchable, hourCycle, minuteStep, placeholder, align, className, style, triggerLabel, disabled, }: TimeRangePickerProps): react_jsx_runtime.JSX.Element;
62
+
63
+ export { AnalogClock as A, TimePicker as T, type AnalogClockProps as a, type TimePickerProps as b, type TimePickerVariant as c, TimeRangePicker as d, type TimeRangePickerProps as e, type TimeRangeValue as f, type TimeValue as g, formatTime as h, formatTimeRange as i };