use-kbd 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-kbd",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Keyboard-first UX for React: action registration, shortcuts modal, omnibar, and sequences",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -48,19 +48,17 @@
48
48
  "react": "^18.3.1",
49
49
  "tsup": "^8.3.5",
50
50
  "typescript": "^5.7.2",
51
- "typescript-eslint": "^8.50.1",
52
- "vitest": "^2.1.8"
51
+ "typescript-eslint": "^8.50.1"
53
52
  },
54
53
  "peerDependencies": {
55
- "react": ">=18.0.0"
56
- },
57
- "dependencies": {
58
- "@rdub/base": "^0.8.1"
54
+ "react": ">=18.0.0",
55
+ "react-dom": ">=18.0.0"
59
56
  },
57
+ "dependencies": {},
60
58
  "scripts": {
61
59
  "build": "tsup",
62
60
  "dev": "tsup --watch",
63
- "test": "vitest",
61
+ "test": "pnpm -C site test",
64
62
  "lint": "eslint src"
65
63
  }
66
64
  }
package/src/styles.css CHANGED
@@ -9,37 +9,47 @@
9
9
  * }
10
10
  */
11
11
 
12
- /* === CSS Custom Properties === */
13
- .kbd-modal,
14
- .kbd-omnibar {
15
- /* Colors */
16
- --kbd-bg: var(--kbd-bg, #ffffff);
17
- --kbd-bg-secondary: var(--kbd-bg-secondary, #f9fafb);
18
- --kbd-text: var(--kbd-text, #1f2937);
19
- --kbd-text-secondary: var(--kbd-text-secondary, #6b7280);
20
- --kbd-border: var(--kbd-border, #e5e7eb);
21
- --kbd-accent: var(--kbd-accent, #3b82f6);
22
- --kbd-accent-hover: var(--kbd-accent-hover, #2563eb);
12
+ /* === CSS Custom Properties (defaults) === */
13
+ :root {
14
+ /* Colors - light mode defaults */
15
+ --kbd-bg: #ffffff;
16
+ --kbd-bg-secondary: #f9fafb;
17
+ --kbd-text: #1f2937;
18
+ --kbd-text-secondary: #6b7280;
19
+ --kbd-border: #e5e7eb;
20
+ --kbd-accent: #3b82f6;
21
+ --kbd-accent-hover: #2563eb;
23
22
 
24
23
  /* Conflict/warning colors */
25
- --kbd-conflict: var(--kbd-conflict, #ef4444);
26
- --kbd-conflict-bg: var(--kbd-conflict-bg, #fef2f2);
27
- --kbd-warning: var(--kbd-warning, #f59e0b);
28
- --kbd-warning-bg: var(--kbd-warning-bg, #fef3c7);
24
+ --kbd-conflict: #ef4444;
25
+ --kbd-conflict-bg: #fef2f2;
26
+ --kbd-warning: #f59e0b;
27
+ --kbd-warning-bg: #fef3c7;
28
+
29
+ /* Timeout bar */
30
+ --kbd-timeout-bar: #10b981;
29
31
 
30
32
  /* Kbd element */
31
- --kbd-kbd-bg: var(--kbd-kbd-bg, #f3f4f6);
32
- --kbd-kbd-border: var(--kbd-kbd-border, #d1d5db);
33
- --kbd-kbd-text: var(--kbd-kbd-text, #374151);
33
+ --kbd-kbd-bg: #f3f4f6;
34
+ --kbd-kbd-border: #d1d5db;
35
+ --kbd-kbd-text: #374151;
34
36
 
35
37
  /* Spacing & sizing */
36
- --kbd-radius: var(--kbd-radius, 8px);
37
- --kbd-radius-sm: var(--kbd-radius-sm, 4px);
38
- --kbd-gap: var(--kbd-gap, 8px);
39
- --kbd-padding: var(--kbd-padding, 16px);
38
+ --kbd-radius: 8px;
39
+ --kbd-radius-sm: 4px;
40
+ --kbd-gap: 8px;
41
+ --kbd-padding: 16px;
40
42
 
41
43
  /* Animation */
42
- --kbd-transition: var(--kbd-transition, 150ms ease);
44
+ --kbd-transition: 150ms ease;
45
+
46
+ /* Sequence modal */
47
+ --kbd-sequence-max-height: 300px;
48
+ --kbd-sequence-row-padding: 8px 12px;
49
+ --kbd-sequence-row-gap: 8px;
50
+
51
+ /* Omnibar */
52
+ --kbd-omnibar-max-height: 50vh;
43
53
  }
44
54
 
45
55
  /* === Backdrop === */
@@ -79,12 +89,34 @@
79
89
  font-weight: 600;
80
90
  }
81
91
 
92
+ .kbd-modal-header-buttons {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: 8px;
96
+ }
97
+
98
+ .kbd-reset-btn {
99
+ background: none;
100
+ border: 1px solid var(--kbd-border);
101
+ border-radius: var(--kbd-radius-sm);
102
+ padding: 4px 10px;
103
+ font-size: 0.875rem;
104
+ cursor: pointer;
105
+ color: var(--kbd-text-secondary);
106
+ transition: all var(--kbd-transition);
107
+ }
108
+
109
+ .kbd-reset-btn:hover {
110
+ color: var(--kbd-text);
111
+ border-color: var(--kbd-text-secondary);
112
+ }
113
+
82
114
  .kbd-modal-close {
83
115
  background: none;
84
116
  border: none;
85
- font-size: 1.5rem;
117
+ font-size: 1.25rem;
86
118
  cursor: pointer;
87
- padding: 4px;
119
+ padding: 4px 8px;
88
120
  line-height: 1;
89
121
  color: var(--kbd-text-secondary);
90
122
  transition: color var(--kbd-transition);
@@ -136,6 +168,7 @@
136
168
 
137
169
  /* === Kbd element === */
138
170
  .kbd-kbd {
171
+ position: relative;
139
172
  display: inline-flex;
140
173
  align-items: center;
141
174
  gap: 2px;
@@ -202,12 +235,47 @@
202
235
  flex-shrink: 0;
203
236
  }
204
237
 
238
+ /* === Key icons (arrows, enter, etc.) === */
239
+ .kbd-key-icon {
240
+ width: 14px;
241
+ height: 14px;
242
+ flex-shrink: 0;
243
+ }
244
+
245
+ /* === Digit placeholder (for \d and \d+ patterns) === */
246
+ .kbd-placeholder {
247
+ font-family: monospace;
248
+ font-weight: 600;
249
+ color: var(--kbd-text);
250
+ background: var(--kbd-bg-secondary);
251
+ border: 1px solid var(--kbd-border);
252
+ border-radius: var(--kbd-radius-sm);
253
+ padding: 0 3px;
254
+ margin: 0 1px;
255
+ }
256
+
205
257
  /* === Sequence separator === */
206
258
  .kbd-sequence-sep {
207
259
  color: var(--kbd-text-secondary);
208
260
  margin: 0 1px;
209
261
  }
210
262
 
263
+ /* === Clickable kbd === */
264
+ .kbd-clickable {
265
+ cursor: pointer;
266
+ transition: all var(--kbd-transition);
267
+ }
268
+
269
+ .kbd-clickable:hover {
270
+ background: var(--kbd-accent);
271
+ color: white;
272
+ }
273
+
274
+ .kbd-clickable:focus-visible {
275
+ outline: 2px solid var(--kbd-accent);
276
+ outline-offset: 2px;
277
+ }
278
+
211
279
  /* === Add binding button === */
212
280
  .kbd-add-btn {
213
281
  background: none;
@@ -227,23 +295,39 @@
227
295
 
228
296
  /* === Remove binding button === */
229
297
  .kbd-remove-btn {
230
- background: none;
298
+ position: absolute;
299
+ top: -6px;
300
+ right: -6px;
301
+ display: flex;
302
+ align-items: center;
303
+ justify-content: center;
304
+ background: var(--kbd-conflict, #dc2626);
231
305
  border: none;
306
+ border-radius: 50%;
232
307
  padding: 0;
233
- margin-left: 2px;
234
- font-size: 0.875rem;
235
- color: var(--kbd-text-secondary);
308
+ width: 16px;
309
+ height: 16px;
310
+ font-size: 12px;
311
+ font-weight: bold;
312
+ line-height: 1;
313
+ color: #fff;
236
314
  cursor: pointer;
237
315
  opacity: 0;
238
- transition: opacity var(--kbd-transition);
316
+ transform: scale(0.8);
317
+ transition:
318
+ opacity var(--kbd-transition),
319
+ transform var(--kbd-transition),
320
+ background var(--kbd-transition);
239
321
  }
240
322
 
241
- .kbd-kbd:hover .kbd-remove-btn {
323
+ .kbd-kbd:hover .kbd-remove-btn,
324
+ .kbd-kbd:focus-within .kbd-remove-btn {
242
325
  opacity: 1;
326
+ transform: scale(1);
243
327
  }
244
328
 
245
329
  .kbd-remove-btn:hover {
246
- color: var(--kbd-conflict);
330
+ background: #b91c1c;
247
331
  }
248
332
 
249
333
  /* === Timeout bar === */
@@ -251,14 +335,14 @@
251
335
  position: absolute;
252
336
  bottom: 0;
253
337
  left: 0;
254
- height: 2px;
255
- background-color: var(--kbd-accent);
338
+ height: 3px;
339
+ background-color: var(--kbd-timeout-bar);
256
340
  animation: kbd-timeout linear forwards;
257
341
  }
258
342
 
259
343
  @keyframes kbd-timeout {
260
344
  from { width: 100%; }
261
- to { width: 0%; }
345
+ to { width: 0; }
262
346
  }
263
347
 
264
348
  /* === Omnibar === */
@@ -298,7 +382,7 @@
298
382
  }
299
383
 
300
384
  .kbd-omnibar-results {
301
- max-height: 300px;
385
+ max-height: var(--kbd-omnibar-max-height);
302
386
  overflow-y: auto;
303
387
  }
304
388
 
@@ -343,6 +427,43 @@
343
427
  font-size: 0.875rem;
344
428
  }
345
429
 
430
+ .kbd-omnibar-loading {
431
+ padding: var(--kbd-padding);
432
+ text-align: center;
433
+ color: var(--kbd-text-secondary);
434
+ font-size: 0.875rem;
435
+ }
436
+
437
+ .kbd-omnibar-pagination {
438
+ padding: 8px var(--kbd-padding);
439
+ text-align: center;
440
+ font-size: 0.75rem;
441
+ color: var(--kbd-text-secondary);
442
+ border-top: 1px solid var(--kbd-border);
443
+ }
444
+
445
+ .kbd-omnibar-pagination-loading {
446
+ font-style: italic;
447
+ }
448
+
449
+ .kbd-omnibar-pagination-info {
450
+ /* Normal weight for "X of Y" */
451
+ }
452
+
453
+ .kbd-omnibar-pagination-more {
454
+ font-style: italic;
455
+ }
456
+
457
+ .kbd-omnibar-result-description {
458
+ font-size: 0.75rem;
459
+ color: var(--kbd-text-secondary);
460
+ margin-left: auto;
461
+ white-space: nowrap;
462
+ overflow: hidden;
463
+ text-overflow: ellipsis;
464
+ max-width: 150px;
465
+ }
466
+
346
467
  /* === Sequence Modal === */
347
468
  .kbd-sequence-backdrop {
348
469
  position: fixed;
@@ -362,18 +483,6 @@
362
483
  }
363
484
 
364
485
  .kbd-sequence {
365
- /* Inherit CSS custom properties */
366
- --kbd-bg: var(--kbd-bg, #ffffff);
367
- --kbd-bg-secondary: var(--kbd-bg-secondary, #f9fafb);
368
- --kbd-text: var(--kbd-text, #1f2937);
369
- --kbd-text-secondary: var(--kbd-text-secondary, #6b7280);
370
- --kbd-border: var(--kbd-border, #e5e7eb);
371
- --kbd-kbd-bg: var(--kbd-kbd-bg, #f3f4f6);
372
- --kbd-kbd-border: var(--kbd-kbd-border, #d1d5db);
373
- --kbd-kbd-text: var(--kbd-kbd-text, #374151);
374
- --kbd-radius: var(--kbd-radius, 8px);
375
- --kbd-accent: var(--kbd-accent, #3b82f6);
376
-
377
486
  background-color: var(--kbd-bg);
378
487
  border: 1px solid var(--kbd-border);
379
488
  border-radius: 12px;
@@ -447,8 +556,8 @@
447
556
  padding: 12px 16px;
448
557
  display: flex;
449
558
  flex-direction: column;
450
- gap: 8px;
451
- max-height: 300px;
559
+ gap: var(--kbd-sequence-row-gap);
560
+ max-height: var(--kbd-sequence-max-height);
452
561
  overflow-y: auto;
453
562
  }
454
563
 
@@ -456,14 +565,19 @@
456
565
  display: flex;
457
566
  align-items: center;
458
567
  gap: 8px;
459
- padding: 8px 12px;
460
- background-color: var(--kbd-bg-secondary);
568
+ padding: var(--kbd-sequence-row-padding);
461
569
  border-radius: 6px;
462
570
  transition: background-color 0.1s ease;
463
571
  }
464
572
 
465
573
  .kbd-sequence-completion:hover {
466
- background-color: var(--kbd-bg);
574
+ background-color: var(--kbd-bg-secondary);
575
+ }
576
+
577
+ .kbd-sequence-completion.selected {
578
+ background-color: var(--kbd-bg-secondary);
579
+ outline: 2px solid var(--kbd-accent);
580
+ outline-offset: -2px;
467
581
  }
468
582
 
469
583
  .kbd-sequence-arrow {
@@ -508,19 +622,140 @@
508
622
  text-align: center;
509
623
  }
510
624
 
625
+ /* === Lookup Modal === */
626
+ .kbd-lookup-backdrop {
627
+ position: fixed;
628
+ inset: 0;
629
+ display: flex;
630
+ align-items: flex-start;
631
+ justify-content: center;
632
+ padding-top: 15vh;
633
+ z-index: 9999;
634
+ background-color: rgba(0, 0, 0, 0.5);
635
+ animation: kbd-fade-in 0.1s ease;
636
+ }
637
+
638
+ .kbd-lookup {
639
+ background-color: var(--kbd-bg);
640
+ border-radius: var(--kbd-radius);
641
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
642
+ width: 90vw;
643
+ max-width: 500px;
644
+ max-height: 70vh;
645
+ display: flex;
646
+ flex-direction: column;
647
+ overflow: hidden;
648
+ }
649
+
650
+ .kbd-lookup-header {
651
+ padding: 12px 16px;
652
+ border-bottom: 1px solid var(--kbd-border);
653
+ display: flex;
654
+ flex-direction: column;
655
+ gap: 8px;
656
+ }
657
+
658
+ .kbd-lookup-search {
659
+ display: flex;
660
+ align-items: center;
661
+ min-height: 32px;
662
+ }
663
+
664
+ .kbd-lookup-placeholder {
665
+ color: var(--kbd-text-secondary);
666
+ font-size: 0.875rem;
667
+ }
668
+
669
+ .kbd-lookup-hint {
670
+ font-size: 0.75rem;
671
+ color: var(--kbd-text-secondary);
672
+ }
673
+
674
+ .kbd-lookup-results {
675
+ flex: 1;
676
+ overflow-y: auto;
677
+ padding: 8px;
678
+ }
679
+
680
+ .kbd-lookup-result {
681
+ display: flex;
682
+ align-items: center;
683
+ gap: 12px;
684
+ padding: 8px 12px;
685
+ border-radius: 6px;
686
+ cursor: pointer;
687
+ transition: background-color 0.1s ease;
688
+ }
689
+
690
+ .kbd-lookup-result:hover,
691
+ .kbd-lookup-result.selected {
692
+ background-color: var(--kbd-bg-secondary);
693
+ }
694
+
695
+ .kbd-lookup-result.selected {
696
+ outline: 2px solid var(--kbd-accent);
697
+ outline-offset: -2px;
698
+ }
699
+
700
+ .kbd-lookup-binding {
701
+ display: flex;
702
+ gap: 4px;
703
+ }
704
+
705
+ .kbd-lookup-labels {
706
+ flex: 1;
707
+ font-size: 0.875rem;
708
+ color: var(--kbd-text);
709
+ }
710
+
711
+ .kbd-lookup-empty {
712
+ padding: 24px;
713
+ text-align: center;
714
+ color: var(--kbd-text-secondary);
715
+ font-size: 0.875rem;
716
+ }
717
+
718
+ .kbd-lookup-continuations {
719
+ padding: 8px 16px;
720
+ border-top: 1px solid var(--kbd-border);
721
+ display: flex;
722
+ align-items: center;
723
+ gap: 8px;
724
+ flex-wrap: wrap;
725
+ }
726
+
727
+ .kbd-lookup-continuations-label {
728
+ font-size: 0.75rem;
729
+ color: var(--kbd-text-secondary);
730
+ }
731
+
732
+ .kbd-kbd.kbd-small {
733
+ font-size: 0.7rem;
734
+ padding: 2px 5px;
735
+ }
736
+
511
737
  /* === Dark mode preset === */
512
- [data-theme="dark"] .kbd-modal,
513
- [data-theme="dark"] .kbd-omnibar,
514
- [data-theme="dark"] .kbd-sequence,
515
- .dark .kbd-modal,
516
- .dark .kbd-omnibar,
517
- .dark .kbd-sequence {
518
- --kbd-bg: var(--kbd-bg, #1f2937);
519
- --kbd-bg-secondary: var(--kbd-bg-secondary, #374151);
520
- --kbd-text: var(--kbd-text, #f3f4f6);
521
- --kbd-text-secondary: var(--kbd-text-secondary, #9ca3af);
522
- --kbd-border: var(--kbd-border, #4b5563);
523
- --kbd-kbd-bg: var(--kbd-kbd-bg, #374151);
524
- --kbd-kbd-border: var(--kbd-kbd-border, #4b5563);
525
- --kbd-kbd-text: var(--kbd-kbd-text, #e5e7eb);
738
+ [data-theme="dark"],
739
+ .dark {
740
+ --kbd-bg: #1f2937;
741
+ --kbd-bg-secondary: #374151;
742
+ --kbd-text: #f3f4f6;
743
+ --kbd-text-secondary: #9ca3af;
744
+ --kbd-border: #4b5563;
745
+ --kbd-kbd-bg: #374151;
746
+ --kbd-kbd-border: #4b5563;
747
+ --kbd-kbd-text: #e5e7eb;
748
+ }
749
+
750
+ @media (prefers-color-scheme: dark) {
751
+ :root:not([data-theme="light"]):not(.light) {
752
+ --kbd-bg: #1f2937;
753
+ --kbd-bg-secondary: #374151;
754
+ --kbd-text: #f3f4f6;
755
+ --kbd-text-secondary: #9ca3af;
756
+ --kbd-border: #4b5563;
757
+ --kbd-kbd-bg: #374151;
758
+ --kbd-kbd-border: #4b5563;
759
+ --kbd-kbd-text: #e5e7eb;
760
+ }
526
761
  }