canny-ui 1.0.0 → 3.0.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.
@@ -0,0 +1,511 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // DatePicker — enterprise-compact unified picker.
4
+ // Modes: date · month · year · time · datetime.
5
+ // Panel is portaled to document.body so it never gets clipped by overflow:hidden ancestors.
6
+
7
+ @keyframes cny-dp-pop {
8
+ from { opacity: 0; transform: translateY(-4px) scale(0.985); }
9
+ to { opacity: 1; transform: translateY(0) scale(1); }
10
+ }
11
+
12
+ // ─────────────────────────────────────────────────────────────────
13
+ // Trigger — stays in its original DOM position
14
+ // ─────────────────────────────────────────────────────────────────
15
+
16
+ .cny-datepicker {
17
+ position: relative;
18
+ display: inline-block;
19
+ width: 100%;
20
+
21
+ &.is-disabled { pointer-events: none; }
22
+
23
+ &-trigger {
24
+ display: inline-flex;
25
+ align-items: center;
26
+ gap: $space-2;
27
+ width: 100%;
28
+ text-align: left;
29
+ cursor: pointer;
30
+ padding: 0 $space-3;
31
+
32
+ &:focus,
33
+ &.is-open { border-color: var(--border-focus); }
34
+ &[disabled] { cursor: not-allowed; }
35
+ }
36
+
37
+ &-icon {
38
+ display: inline-flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ color: var(--text-muted);
42
+ flex: none;
43
+ transition: color 140ms ease;
44
+ }
45
+ &-trigger:hover &-icon,
46
+ &-trigger.is-open &-icon { color: var(--border-focus); }
47
+ &-trigger.danger &-icon { color: var(--color-danger); }
48
+
49
+ &-value {
50
+ flex: 1 1 auto;
51
+ min-width: 0;
52
+ color: var(--text-primary);
53
+ font-size: inherit;
54
+ line-height: 1.4;
55
+ white-space: nowrap;
56
+ overflow: hidden;
57
+ text-overflow: ellipsis;
58
+ font-variant-numeric: tabular-nums;
59
+
60
+ &.is-placeholder { color: var(--text-muted); }
61
+ }
62
+
63
+ &-clear {
64
+ flex: none;
65
+ display: inline-flex;
66
+ align-items: center;
67
+ justify-content: center;
68
+ width: 18px;
69
+ height: 18px;
70
+ border-radius: var(--radius-xs);
71
+ color: var(--text-muted);
72
+ cursor: pointer;
73
+ transition: color 120ms ease, background-color 120ms ease;
74
+
75
+ &:hover { color: var(--text-primary); background: var(--bg-surface-low); }
76
+ }
77
+
78
+ &-error {
79
+ display: block;
80
+ margin-top: 6px;
81
+ font-size: 0.75rem;
82
+ color: var(--color-danger);
83
+ line-height: 1.4;
84
+ }
85
+ }
86
+
87
+ // ─────────────────────────────────────────────────────────────────
88
+ // Portaled panel — rendered into document.body
89
+ // Escapes overflow:hidden / transform / contain ancestors.
90
+ // ─────────────────────────────────────────────────────────────────
91
+
92
+ .cny-datepicker-panel {
93
+ position: fixed;
94
+ z-index: 1000;
95
+ padding: 10px;
96
+ background: var(--bg-surface-lowest);
97
+ border: 1px solid var(--border-color);
98
+ border-radius: var(--radius-sm);
99
+ box-shadow:
100
+ 0 1px 0 rgba(15, 23, 42, 0.02),
101
+ 0 6px 18px -6px rgba(15, 23, 42, 0.14),
102
+ 0 14px 32px -16px rgba(15, 23, 42, 0.16);
103
+ font-family: var(--font-body);
104
+ color: var(--text-primary);
105
+ animation: cny-dp-pop 140ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
106
+
107
+ &.is-date,
108
+ &.is-month,
109
+ &.is-year { width: 264px; }
110
+ &.is-time { width: 208px; }
111
+ &.is-datetime { width: 264px; }
112
+
113
+ max-width: calc(100vw - 16px);
114
+
115
+ @media (prefers-reduced-motion: reduce) { animation: none; }
116
+ }
117
+
118
+ // Panel-internal elements (all portaled together)
119
+
120
+ .cny-datepicker-body {
121
+ display: flex;
122
+ flex-direction: column;
123
+ gap: 8px;
124
+ }
125
+
126
+ .cny-datepicker-header {
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: space-between;
130
+ gap: $space-1;
131
+ margin-bottom: 2px;
132
+ }
133
+
134
+ .cny-datepicker-title {
135
+ font-weight: 600;
136
+ font-size: 0.8125rem;
137
+ letter-spacing: -0.003em;
138
+ color: var(--text-primary);
139
+ flex: 1 1 auto;
140
+ text-align: center;
141
+ font-variant-numeric: tabular-nums;
142
+
143
+ // Click-to-drill-up button variant
144
+ &.is-clickable {
145
+ display: inline-flex;
146
+ align-items: center;
147
+ justify-content: center;
148
+ gap: 4px;
149
+ height: 24px;
150
+ padding: 0 8px;
151
+ background: transparent;
152
+ border: 0;
153
+ border-radius: var(--radius-xs);
154
+ color: inherit;
155
+ font: inherit;
156
+ letter-spacing: inherit;
157
+ cursor: pointer;
158
+ transition: background-color 120ms ease, color 120ms ease;
159
+
160
+ &::after {
161
+ content: '';
162
+ width: 5px;
163
+ height: 5px;
164
+ border-right: 1.5px solid currentColor;
165
+ border-bottom: 1.5px solid currentColor;
166
+ transform: rotate(45deg) translateY(-1px);
167
+ opacity: 0.5;
168
+ transition: opacity 120ms ease, transform 120ms ease;
169
+ }
170
+
171
+ &:hover {
172
+ background: var(--bg-surface-low);
173
+ color: var(--color-info);
174
+
175
+ &::after { opacity: 1; transform: rotate(45deg) translateY(0); }
176
+ }
177
+
178
+ &:focus-visible {
179
+ outline: 2px solid var(--border-focus);
180
+ outline-offset: 1px;
181
+ }
182
+ }
183
+ }
184
+
185
+ .cny-datepicker-nav {
186
+ flex: none;
187
+ display: inline-flex;
188
+ align-items: center;
189
+ justify-content: center;
190
+ width: 24px;
191
+ height: 24px;
192
+ border: 0;
193
+ background: transparent;
194
+ color: var(--text-secondary);
195
+ border-radius: var(--radius-xs);
196
+ cursor: pointer;
197
+ transition: background-color 120ms ease, color 120ms ease;
198
+
199
+ &:hover:not(:disabled) { background: var(--bg-surface-low); color: var(--text-primary); }
200
+ &:active:not(:disabled) { transform: scale(0.92); }
201
+ &:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 1px; }
202
+ &:disabled { opacity: 0.35; cursor: not-allowed; }
203
+ }
204
+
205
+ // ── Calendar grid ────────────────────────────────────────────────
206
+ .cny-datepicker-grid { display: grid; gap: 1px; }
207
+
208
+ .cny-datepicker-weekdays,
209
+ .cny-datepicker-row {
210
+ display: grid;
211
+ grid-template-columns: repeat(7, 1fr);
212
+ gap: 1px;
213
+ }
214
+
215
+ .cny-datepicker-weekday {
216
+ text-align: center;
217
+ font-size: 0.625rem;
218
+ font-weight: 600;
219
+ letter-spacing: 0.04em;
220
+ text-transform: uppercase;
221
+ color: var(--text-muted);
222
+ padding: 4px 0 6px;
223
+ line-height: 1;
224
+ }
225
+
226
+ .cny-datepicker-cell {
227
+ position: relative;
228
+ display: inline-flex;
229
+ align-items: center;
230
+ justify-content: center;
231
+ height: 30px;
232
+ min-width: 0;
233
+ width: 100%;
234
+ border: 0;
235
+ background: transparent;
236
+ color: var(--text-primary);
237
+ font-size: 0.8125rem;
238
+ font-variant-numeric: tabular-nums;
239
+ border-radius: var(--radius-xs);
240
+ cursor: pointer;
241
+ transition: background-color 100ms ease, color 100ms ease;
242
+
243
+ &:hover:not(:disabled):not(.is-selected) { background: var(--bg-surface-low); }
244
+ &:focus-visible { outline: 2px solid var(--border-focus); outline-offset: -1px; z-index: 1; }
245
+
246
+ &.is-outside { color: var(--text-muted); opacity: 0.5; }
247
+
248
+ &.is-today:not(.is-selected) {
249
+ color: var(--color-info);
250
+ font-weight: 600;
251
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-info) 35%, transparent);
252
+
253
+ &::after {
254
+ content: '';
255
+ position: absolute;
256
+ bottom: 3px;
257
+ left: 50%;
258
+ width: 3px;
259
+ height: 3px;
260
+ border-radius: 50%;
261
+ background: currentColor;
262
+ transform: translateX(-50%);
263
+ }
264
+ }
265
+
266
+ &.is-selected {
267
+ background: var(--color-info);
268
+ color: var(--color-info-on);
269
+ font-weight: 600;
270
+ }
271
+
272
+ &:disabled { color: var(--text-disabled); cursor: not-allowed; opacity: 0.4; }
273
+ }
274
+
275
+ // ── Month / year chip grids ─────────────────────────────────────
276
+ .cny-datepicker-chip-grid {
277
+ display: grid;
278
+ grid-template-columns: repeat(3, 1fr);
279
+ gap: 4px;
280
+ padding: 2px 0;
281
+ }
282
+
283
+ .cny-datepicker-chip {
284
+ display: inline-flex;
285
+ align-items: center;
286
+ justify-content: center;
287
+ height: 34px;
288
+ padding: 0 $space-2;
289
+ border: 1px solid transparent;
290
+ background: transparent;
291
+ color: var(--text-primary);
292
+ font-size: 0.8125rem;
293
+ font-variant-numeric: tabular-nums;
294
+ border-radius: var(--radius-xs);
295
+ cursor: pointer;
296
+ transition: background-color 120ms ease, color 120ms ease;
297
+
298
+ &:hover:not(:disabled):not(.is-selected) { background: var(--bg-surface-low); }
299
+ &:focus-visible { outline: 2px solid var(--border-focus); outline-offset: -1px; }
300
+
301
+ &.is-outside { color: var(--text-muted); opacity: 0.55; }
302
+
303
+ // Marks the currently-viewed month/year when drilling up for navigation
304
+ &.is-current {
305
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-info) 40%, transparent);
306
+ color: var(--color-info);
307
+ font-weight: 600;
308
+ }
309
+
310
+ &.is-selected {
311
+ background: var(--color-info);
312
+ color: var(--color-info-on);
313
+ font-weight: 600;
314
+ box-shadow: none;
315
+ }
316
+
317
+ &:disabled { color: var(--text-disabled); cursor: not-allowed; opacity: 0.4; }
318
+ }
319
+
320
+ // ─────────────────────────────────────────────────────────────────
321
+ // Compact time picker — stepper digits + segmented AM/PM
322
+ // ─────────────────────────────────────────────────────────────────
323
+
324
+ .cny-dp-time {
325
+ display: flex;
326
+ flex-direction: column;
327
+ align-items: center;
328
+ gap: 6px;
329
+ padding: 6px 0 2px;
330
+ }
331
+
332
+ .cny-datepicker-panel.is-datetime .cny-dp-time {
333
+ padding-top: 8px;
334
+ border-top: 1px solid var(--hairline-color, var(--border-color));
335
+ }
336
+
337
+ .cny-dp-time-row {
338
+ display: inline-flex;
339
+ align-items: center;
340
+ gap: 4px;
341
+ }
342
+
343
+ .cny-dp-time-sep {
344
+ font-size: 1rem;
345
+ font-weight: 600;
346
+ color: var(--text-muted);
347
+ font-variant-numeric: tabular-nums;
348
+ padding: 0 1px;
349
+ user-select: none;
350
+ line-height: 1;
351
+ transform: translateY(-1px);
352
+ }
353
+
354
+ // Editable time field — input on the left, stacked ▲/▼ on the right.
355
+ .cny-dp-timefield {
356
+ display: inline-flex;
357
+ align-items: stretch;
358
+ height: 30px;
359
+ background: var(--bg-surface-lowest);
360
+ border: 1px solid var(--border-color);
361
+ border-radius: var(--radius-xs);
362
+ overflow: hidden;
363
+ transition: border-color 120ms ease, box-shadow 120ms ease;
364
+
365
+ &:hover { border-color: var(--border-color-strong, var(--border-color)); }
366
+
367
+ &:focus-within {
368
+ border-color: var(--border-focus);
369
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--border-focus) 18%, transparent);
370
+ }
371
+
372
+ &-input {
373
+ width: 32px;
374
+ height: 100%;
375
+ padding: 0 2px;
376
+ text-align: center;
377
+ font-family: var(--font-body);
378
+ font-size: 0.875rem;
379
+ font-weight: 600;
380
+ font-variant-numeric: tabular-nums;
381
+ color: var(--text-primary);
382
+ background: transparent;
383
+ border: 0;
384
+ outline: none;
385
+ line-height: 1;
386
+
387
+ // Hide spinners if a browser renders type=text as numeric
388
+ -moz-appearance: textfield;
389
+ &::-webkit-outer-spin-button,
390
+ &::-webkit-inner-spin-button {
391
+ -webkit-appearance: none;
392
+ margin: 0;
393
+ }
394
+
395
+ &::selection { background: color-mix(in srgb, var(--color-info) 30%, transparent); }
396
+ }
397
+
398
+ &-controls {
399
+ display: flex;
400
+ flex-direction: column;
401
+ border-left: 1px solid var(--hairline-color, var(--border-color));
402
+ }
403
+
404
+ &-btn {
405
+ flex: 1 1 0;
406
+ display: inline-flex;
407
+ align-items: center;
408
+ justify-content: center;
409
+ width: 18px;
410
+ padding: 0;
411
+ border: 0;
412
+ background: transparent;
413
+ color: var(--text-muted);
414
+ cursor: pointer;
415
+ transition: background-color 100ms ease, color 100ms ease;
416
+
417
+ &:first-child { border-bottom: 1px solid var(--hairline-color, var(--border-color)); }
418
+
419
+ &:hover {
420
+ background: var(--bg-surface-low);
421
+ color: var(--text-primary);
422
+ }
423
+ &:active { background: var(--bg-surface-edge, var(--bg-surface-low)); }
424
+ }
425
+ }
426
+
427
+ .cny-dp-ampm {
428
+ display: inline-flex;
429
+ align-items: center;
430
+ margin-left: 6px;
431
+ padding: 2px;
432
+ background: var(--bg-surface-low);
433
+ border: 1px solid var(--hairline-color, var(--border-color));
434
+ border-radius: var(--radius-xs);
435
+
436
+ &-btn {
437
+ display: inline-flex;
438
+ align-items: center;
439
+ justify-content: center;
440
+ height: 20px;
441
+ min-width: 26px;
442
+ padding: 0 6px;
443
+ font-family: var(--font-body);
444
+ font-size: 0.6875rem;
445
+ font-weight: 600;
446
+ letter-spacing: 0.04em;
447
+ color: var(--text-muted);
448
+ background: transparent;
449
+ border: 0;
450
+ border-radius: 2px;
451
+ cursor: pointer;
452
+ transition: background-color 120ms ease, color 120ms ease;
453
+
454
+ &:hover:not(.is-active) { color: var(--text-primary); }
455
+
456
+ &.is-active {
457
+ background: var(--bg-surface-lowest);
458
+ color: var(--color-info);
459
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08), inset 0 0 0 1px var(--hairline-color, var(--border-color));
460
+ }
461
+
462
+ &:focus-visible {
463
+ outline: 2px solid var(--border-focus);
464
+ outline-offset: 1px;
465
+ }
466
+ }
467
+ }
468
+
469
+ // ─────────────────────────────────────────────────────────────────
470
+ // Footer
471
+ // ─────────────────────────────────────────────────────────────────
472
+
473
+ .cny-datepicker-footer {
474
+ display: flex;
475
+ align-items: center;
476
+ gap: 4px;
477
+ margin-top: 8px;
478
+ padding-top: 6px;
479
+ border-top: 1px solid var(--hairline-color, var(--border-color));
480
+ }
481
+
482
+ .cny-datepicker-footer-spacer { flex: 1 1 auto; }
483
+
484
+ .cny-datepicker-action {
485
+ border: 0;
486
+ background: transparent;
487
+ color: var(--color-info);
488
+ font-family: var(--font-body);
489
+ font-size: 0.75rem;
490
+ font-weight: 500;
491
+ padding: 4px 8px;
492
+ border-radius: var(--radius-xs);
493
+ cursor: pointer;
494
+ transition: background-color 120ms ease;
495
+
496
+ &:hover:not(:disabled) { background: color-mix(in srgb, var(--color-info) 10%, transparent); }
497
+ &:disabled { color: var(--text-disabled); cursor: not-allowed; }
498
+ &:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 1px; }
499
+
500
+ &.is-primary {
501
+ background: var(--color-info);
502
+ color: var(--color-info-on);
503
+ padding: 4px 10px;
504
+ font-weight: 600;
505
+
506
+ &:hover:not(:disabled) {
507
+ background: var(--color-info-hover, var(--color-info));
508
+ filter: brightness(1.05);
509
+ }
510
+ }
511
+ }
@@ -0,0 +1,113 @@
1
+ @use '../tokens/primitives' as *;
2
+
3
+ // Dialog (Modal) — portaled, centered, focus-trapped.
4
+
5
+ @keyframes cny-dialog-overlay-in { from { opacity: 0; } to { opacity: 1; } }
6
+ @keyframes cny-dialog-content-in {
7
+ from { opacity: 0; transform: translate(-50%, -48%) scale(0.97); }
8
+ to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
9
+ }
10
+
11
+ .cny-dialog-overlay {
12
+ position: fixed;
13
+ inset: 0;
14
+ background: rgba(9, 9, 11, 0.55);
15
+ z-index: 1000;
16
+ backdrop-filter: blur(2px);
17
+ -webkit-backdrop-filter: blur(2px);
18
+ animation: cny-dialog-overlay-in 160ms ease-out;
19
+ }
20
+ .cny-dark .cny-dialog-overlay { background: rgba(0, 0, 0, 0.7); }
21
+
22
+ .cny-dialog-content {
23
+ position: fixed;
24
+ top: 50%;
25
+ left: 50%;
26
+ transform: translate(-50%, -50%);
27
+ z-index: 1001;
28
+ display: flex;
29
+ flex-direction: column;
30
+ max-height: calc(100vh - 32px);
31
+ background: var(--bg-surface-lowest);
32
+ border: 1px solid var(--border-color);
33
+ border-radius: var(--radius-md);
34
+ box-shadow:
35
+ 0 24px 60px -24px rgba(0, 0, 0, 0.45),
36
+ 0 0 0 1px var(--border-color);
37
+ font-family: var(--font-body);
38
+ color: var(--text-primary);
39
+ animation: cny-dialog-content-in 200ms cubic-bezier(0.2, 0, 0, 1);
40
+ outline: none;
41
+ overflow: hidden;
42
+
43
+ &.size-sm { width: min(420px, calc(100vw - 32px)); }
44
+ &.size-md { width: min(560px, calc(100vw - 32px)); }
45
+ &.size-lg { width: min(760px, calc(100vw - 32px)); }
46
+ &.size-xl { width: min(960px, calc(100vw - 32px)); }
47
+ &.size-full { width: calc(100vw - 32px); height: calc(100vh - 32px); }
48
+ }
49
+
50
+ .cny-dialog-header {
51
+ display: flex;
52
+ align-items: flex-start;
53
+ gap: 12px;
54
+ padding: 18px 20px 14px;
55
+ border-bottom: 1px solid var(--hairline-color);
56
+ }
57
+
58
+ .cny-dialog-titles { flex: 1 1 auto; min-width: 0; }
59
+ .cny-dialog-title {
60
+ margin: 0;
61
+ font-size: 1rem;
62
+ font-weight: 650;
63
+ letter-spacing: -0.005em;
64
+ color: var(--text-primary);
65
+ }
66
+ .cny-dialog-description {
67
+ margin: 4px 0 0;
68
+ font-size: 0.875rem;
69
+ line-height: 1.55;
70
+ color: var(--text-secondary);
71
+ }
72
+
73
+ .cny-dialog-close {
74
+ flex: none;
75
+ display: inline-flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ width: 28px;
79
+ height: 28px;
80
+ background: transparent;
81
+ border: 0;
82
+ color: var(--text-muted);
83
+ cursor: pointer;
84
+ border-radius: var(--radius-xs);
85
+ transition: color 120ms ease, background-color 120ms ease;
86
+
87
+ &:hover { color: var(--text-primary); background: var(--bg-surface-low); }
88
+ &:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 1px; }
89
+ }
90
+
91
+ .cny-dialog-body {
92
+ flex: 1 1 auto;
93
+ overflow-y: auto;
94
+ padding: 18px 20px;
95
+ font-size: 0.9375rem;
96
+ line-height: 1.6;
97
+ color: var(--text-secondary);
98
+ }
99
+
100
+ .cny-dialog-footer {
101
+ display: flex;
102
+ align-items: center;
103
+ justify-content: flex-end;
104
+ gap: 8px;
105
+ padding: 14px 20px;
106
+ border-top: 1px solid var(--hairline-color);
107
+ background: var(--bg-surface-lowest);
108
+ }
109
+
110
+ @media (prefers-reduced-motion: reduce) {
111
+ .cny-dialog-overlay,
112
+ .cny-dialog-content { animation: none; }
113
+ }