unmagic-components 0.1.0 → 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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -1
  3. data/README.md +493 -1
  4. data/app/assets/javascripts/unmagic/components/autogrow.js +74 -0
  5. data/app/assets/javascripts/unmagic/components/clipboard.js +63 -0
  6. data/app/assets/javascripts/unmagic/components/confirm.js +102 -0
  7. data/app/assets/javascripts/unmagic/components/dialog.js +53 -0
  8. data/app/assets/javascripts/unmagic/components/menu.js +139 -0
  9. data/app/assets/javascripts/unmagic/components/modal.js +182 -0
  10. data/app/assets/javascripts/unmagic/components/tabs.js +94 -0
  11. data/app/assets/javascripts/unmagic/components/time.js +169 -0
  12. data/app/assets/javascripts/unmagic/components/toasts.js +171 -0
  13. data/app/assets/javascripts/unmagic/components/tooltip.js +133 -0
  14. data/app/assets/javascripts/unmagic/components/uuid_input.js +70 -0
  15. data/app/assets/javascripts/unmagic/components.js +19 -0
  16. data/app/assets/stylesheets/unmagic/components.css +927 -0
  17. data/config/importmap.rb +5 -1
  18. data/lib/unmagic/components/action_view_helpers.rb +373 -0
  19. data/lib/unmagic/components/autogrow.rb +13 -0
  20. data/lib/unmagic/components/badge.rb +21 -0
  21. data/lib/unmagic/components/button.rb +28 -0
  22. data/lib/unmagic/components/callout.rb +60 -0
  23. data/lib/unmagic/components/card.rb +67 -0
  24. data/lib/unmagic/components/configuration.rb +19 -1
  25. data/lib/unmagic/components/confirm_template.rb +46 -0
  26. data/lib/unmagic/components/copy_button.rb +50 -0
  27. data/lib/unmagic/components/detail_list.rb +13 -3
  28. data/lib/unmagic/components/dialog.rb +79 -0
  29. data/lib/unmagic/components/dialog_responder.rb +46 -0
  30. data/lib/unmagic/components/engine.rb +13 -4
  31. data/lib/unmagic/components/form_builder.rb +24 -0
  32. data/lib/unmagic/components/icons.rb +40 -0
  33. data/lib/unmagic/components/local_time.rb +64 -0
  34. data/lib/unmagic/components/menu.rb +82 -0
  35. data/lib/unmagic/components/modal.rb +75 -0
  36. data/lib/unmagic/components/page_header.rb +96 -0
  37. data/lib/unmagic/components/skeleton.rb +97 -0
  38. data/lib/unmagic/components/tabs.rb +95 -0
  39. data/lib/unmagic/components/toast.rb +54 -0
  40. data/lib/unmagic/components/toasts.rb +46 -0
  41. data/lib/unmagic/components/tooltip.rb +32 -0
  42. data/lib/unmagic/components/turbo_stream_actions.rb +19 -0
  43. data/lib/unmagic/components/uuid_input.rb +24 -0
  44. data/lib/unmagic/components/version.rb +1 -1
  45. data/lib/unmagic/components.rb +21 -0
  46. metadata +42 -7
@@ -160,6 +160,65 @@
160
160
  }
161
161
  }
162
162
 
163
+ /* Shapes for blocking out an interface (skeleton, skeleton_text, …). Each is sized
164
+ from what it stands in for, so nothing moves when the real content arrives: a
165
+ text line takes exactly one line box of the font around it, and a button shape
166
+ the height of a real .UnmagicButton. Width and height set on the element win. */
167
+ /* Exactly one line box tall, with the thinner bar centred inside; margins around
168
+ the bar would collapse through its container and lose the height. */
169
+ .UnmagicSkeletonLine {
170
+ display: flex;
171
+ align-items: center;
172
+ max-width: 100%;
173
+ height: 1.2em;
174
+ height: 1lh;
175
+ }
176
+
177
+ .UnmagicSkeleton--text {
178
+ width: 100%;
179
+ height: 0.7em;
180
+ border-radius: 0.25rem;
181
+ }
182
+
183
+ .UnmagicSkeletonText {
184
+ display: block;
185
+ max-width: 100%;
186
+ }
187
+
188
+ .UnmagicSkeleton--circle {
189
+ display: block;
190
+ flex-shrink: 0;
191
+ border-radius: 9999px;
192
+ }
193
+
194
+ .UnmagicSkeleton--block {
195
+ display: block;
196
+ width: 100%;
197
+ max-width: 100%;
198
+ border-radius: 0.5rem;
199
+ }
200
+
201
+ /* A button's height is its vertical padding, its line height and a 1px border
202
+ each side; these match .UnmagicButton at each size. */
203
+ .UnmagicSkeleton--button {
204
+ display: inline-block;
205
+ width: 5.5rem;
206
+ height: calc(2rem + 2px);
207
+ vertical-align: middle;
208
+ border-radius: 0.375rem;
209
+ }
210
+
211
+ .UnmagicSkeleton--button.UnmagicSkeleton--small {
212
+ width: 4.5rem;
213
+ height: calc(1.5rem + 2px);
214
+ }
215
+
216
+ .UnmagicSkeleton--button.UnmagicSkeleton--large {
217
+ width: 7rem;
218
+ height: calc(2.75rem + 2px);
219
+ border-radius: 0.5rem;
220
+ }
221
+
163
222
  /* ------------------------------------------------------------ Empty state */
164
223
 
165
224
  .UnmagicEmptyState {
@@ -362,12 +421,22 @@
362
421
  gap: 0.5rem 1.5rem;
363
422
  }
364
423
 
424
+ /* ---------------------------------------------------------------- Buttons */
425
+
365
426
  /* A plain, unobtrusive button, so an unconfigured form still has one that looks
366
427
  deliberate. Most apps point config.submit_class at their own button helper. */
367
428
  .UnmagicButton {
429
+ display: inline-flex;
430
+ align-items: center;
431
+ justify-content: center;
432
+ gap: 0.5rem;
368
433
  padding: 0.375rem 0.875rem;
434
+ font: inherit;
369
435
  font-size: 0.875rem;
436
+ line-height: 1.25rem;
370
437
  font-weight: 500;
438
+ white-space: nowrap;
439
+ text-decoration: none;
371
440
  border-radius: 0.375rem;
372
441
  cursor: pointer;
373
442
  border: 1px solid var(--unmagic-border-strong, var(--color-neutral-300, #d4d4d4));
@@ -375,8 +444,866 @@
375
444
  color: var(--unmagic-text, var(--color-neutral-900, #171717));
376
445
  }
377
446
 
447
+ .UnmagicButton:hover {
448
+ background-color: var(--unmagic-hover, var(--color-neutral-50, #fafafa));
449
+ }
450
+
451
+ .UnmagicButton:focus-visible {
452
+ outline: 2px solid var(--unmagic-focus, var(--color-neutral-400, #a3a3a3));
453
+ outline-offset: 2px;
454
+ }
455
+
456
+ .UnmagicButton:disabled,
457
+ .UnmagicButton[aria-disabled="true"] {
458
+ opacity: 0.5;
459
+ pointer-events: none;
460
+ }
461
+
378
462
  .UnmagicButton--primary {
379
463
  border-color: transparent;
380
464
  background-color: var(--unmagic-accent, var(--color-neutral-900, #171717));
381
465
  color: var(--unmagic-on-accent, var(--color-white, #fff));
382
466
  }
467
+
468
+ .UnmagicButton--primary:hover {
469
+ background-color: var(--unmagic-accent, var(--color-neutral-900, #171717));
470
+ opacity: 0.9;
471
+ }
472
+
473
+ .UnmagicButton--ghost {
474
+ border-color: transparent;
475
+ background-color: transparent;
476
+ }
477
+
478
+ /* Styled like the default so the two sit level in an actions row; only the label
479
+ and the hover carry the warning. */
480
+ .UnmagicButton--danger {
481
+ color: var(--unmagic-bad, var(--color-red-600, #dc2626));
482
+ }
483
+
484
+ .UnmagicButton--danger:hover {
485
+ background-color: var(--unmagic-bad-surface, var(--color-red-50, #fef2f2));
486
+ }
487
+
488
+ .UnmagicButton--icon {
489
+ padding: 0.375rem;
490
+ border-color: transparent;
491
+ background-color: transparent;
492
+ color: var(--unmagic-text-3, var(--color-neutral-500, #737373));
493
+ }
494
+
495
+ .UnmagicButton--icon:hover {
496
+ color: var(--unmagic-text, var(--color-neutral-900, #171717));
497
+ }
498
+
499
+ .UnmagicButton--small {
500
+ padding: 0.25rem 0.625rem;
501
+ font-size: 0.75rem;
502
+ line-height: 1rem;
503
+ }
504
+
505
+ .UnmagicButton--large {
506
+ padding: 0.625rem 1.25rem;
507
+ font-size: 1rem;
508
+ line-height: 1.5rem;
509
+ border-radius: 0.5rem;
510
+ }
511
+
512
+ /* ------------------------------------------------------------------ Icons */
513
+
514
+ .UnmagicIcon {
515
+ width: 1rem;
516
+ height: 1rem;
517
+ flex-shrink: 0;
518
+ }
519
+
520
+ .UnmagicVisuallyHidden {
521
+ position: absolute;
522
+ width: 1px;
523
+ height: 1px;
524
+ padding: 0;
525
+ margin: -1px;
526
+ overflow: hidden;
527
+ clip-path: inset(50%);
528
+ white-space: nowrap;
529
+ border-width: 0;
530
+ }
531
+
532
+ /* ---------------------------------------------------------------- Dialogs */
533
+
534
+ /* The <dialog> itself is only a transparent positioning box; the panel inside is
535
+ the visible surface. overflow stays visible so a dropdown inside a dialog form
536
+ isn't clipped, which means a dialog keeps its content short enough to fit. */
537
+ .UnmagicDialogBox {
538
+ margin: auto;
539
+ padding: 0;
540
+ max-width: calc(100vw - 2rem);
541
+ max-height: 90dvh;
542
+ overflow: visible;
543
+ color: inherit;
544
+ background: transparent;
545
+ border: 0;
546
+ }
547
+
548
+ .UnmagicDialogBox:focus {
549
+ outline: none;
550
+ }
551
+
552
+ .UnmagicDialogBox::backdrop {
553
+ background-color: var(--unmagic-backdrop, rgb(0 0 0 / 0.45));
554
+ backdrop-filter: blur(4px);
555
+ }
556
+
557
+ .UnmagicDialogBox[open] {
558
+ animation: unmagic-dialog-in 150ms ease-out;
559
+ }
560
+
561
+ @keyframes unmagic-dialog-in {
562
+ from {
563
+ opacity: 0;
564
+ transform: translateY(0.5rem) scale(0.98);
565
+ }
566
+ }
567
+
568
+ @media (prefers-reduced-motion: reduce) {
569
+ .UnmagicDialogBox[open] {
570
+ animation: none;
571
+ }
572
+ }
573
+
574
+ .UnmagicDialog {
575
+ width: min(100vw - 2rem, 32rem);
576
+ font-size: 0.875rem;
577
+ line-height: 1.5;
578
+ color: var(--unmagic-text, var(--color-neutral-900, #171717));
579
+ background-color: var(--unmagic-surface, var(--color-white, #fff));
580
+ border: 1px solid var(--unmagic-border, var(--color-neutral-200, #e5e5e5));
581
+ border-radius: 0.75rem;
582
+ box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
583
+ }
584
+
585
+ .UnmagicDialog--wide {
586
+ width: min(100vw - 2rem, 48rem);
587
+ }
588
+
589
+ /* margin is reset because this is a <header>, and hosts often style bare
590
+ header elements for their page chrome. */
591
+ .UnmagicDialog__header {
592
+ display: flex;
593
+ align-items: center;
594
+ justify-content: space-between;
595
+ gap: 0.75rem;
596
+ margin: 0;
597
+ padding: 1rem 1.25rem 0.5rem;
598
+ }
599
+
600
+ .UnmagicDialog__title {
601
+ margin: 0;
602
+ font-size: 1rem;
603
+ line-height: 1.5rem;
604
+ font-weight: 600;
605
+ }
606
+
607
+ /* Pushed right even when the panel has no title beside it. */
608
+ .UnmagicDialog__close {
609
+ margin-left: auto;
610
+ margin-right: -0.375rem;
611
+ }
612
+
613
+ .UnmagicDialog__body {
614
+ padding: 1rem 1.25rem;
615
+ }
616
+
617
+ .UnmagicDialog__body > :first-child {
618
+ margin-top: 0;
619
+ }
620
+
621
+ .UnmagicDialog__body > :last-child {
622
+ margin-bottom: 0;
623
+ }
624
+
625
+ .UnmagicDialog__footer {
626
+ display: flex;
627
+ align-items: center;
628
+ justify-content: flex-end;
629
+ gap: 0.75rem;
630
+ padding: 0.5rem 1.25rem 1rem;
631
+ }
632
+
633
+ .UnmagicDialog__error {
634
+ display: flex;
635
+ align-items: flex-start;
636
+ gap: 0.75rem;
637
+ color: var(--unmagic-text-2, var(--color-neutral-600, #525252));
638
+ }
639
+
640
+ .UnmagicDialog__error > .UnmagicIcon {
641
+ margin-top: 0.125rem;
642
+ color: var(--unmagic-bad, var(--color-red-500, #ef4444));
643
+ }
644
+
645
+ .UnmagicDialog__error > p {
646
+ margin: 0;
647
+ }
648
+
649
+ .UnmagicDialog__skeleton-field {
650
+ display: grid;
651
+ gap: 0.5rem;
652
+ margin-bottom: 1.25rem;
653
+ }
654
+
655
+ .UnmagicDialog__skeleton-title {
656
+ width: 10rem;
657
+ height: 1.25rem;
658
+ }
659
+
660
+ .UnmagicDialog__skeleton-label {
661
+ width: 6rem;
662
+ height: 0.75rem;
663
+ }
664
+
665
+ .UnmagicDialog__skeleton-input {
666
+ height: 2.25rem;
667
+ border-radius: 0.375rem;
668
+ }
669
+
670
+ .UnmagicDialog__skeleton-button {
671
+ width: 6rem;
672
+ height: 2.25rem;
673
+ border-radius: 0.375rem;
674
+ }
675
+
676
+ /* ----------------------------------------------------------------- Toasts */
677
+
678
+ /* The stack is a manual popover, so the popover defaults (centred, bordered,
679
+ painted) are undone here. It never takes pointer events itself; the toasts
680
+ inside it do. */
681
+ .UnmagicToasts__stack {
682
+ position: fixed;
683
+ inset: 1rem 1rem auto auto;
684
+ z-index: 60;
685
+ display: flex;
686
+ flex-direction: column;
687
+ gap: 0.5rem;
688
+ width: min(100vw - 2rem, 24rem);
689
+ margin: 0;
690
+ padding: 0;
691
+ overflow: visible;
692
+ color: inherit;
693
+ background: transparent;
694
+ border: 0;
695
+ pointer-events: none;
696
+ }
697
+
698
+ .UnmagicToast {
699
+ display: flex;
700
+ align-items: flex-start;
701
+ gap: 0.5rem;
702
+ padding: 0.625rem 0.75rem;
703
+ font-size: 0.875rem;
704
+ line-height: 1.25rem;
705
+ color: var(--unmagic-text, var(--color-neutral-900, #171717));
706
+ background-color: var(--unmagic-surface, var(--color-white, #fff));
707
+ border: 1px solid var(--unmagic-border, var(--color-neutral-200, #e5e5e5));
708
+ border-radius: 0.5rem;
709
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
710
+ pointer-events: auto;
711
+ opacity: 0;
712
+ transform: translateY(-0.5rem);
713
+ transition: opacity 200ms ease, transform 200ms ease;
714
+ }
715
+
716
+ .UnmagicToast[data-open] {
717
+ opacity: 1;
718
+ transform: none;
719
+ }
720
+
721
+ @media (prefers-reduced-motion: reduce) {
722
+ .UnmagicToast {
723
+ transform: none;
724
+ transition: none;
725
+ }
726
+ }
727
+
728
+ .UnmagicToast__icon {
729
+ margin-top: 0.125rem;
730
+ }
731
+
732
+ .UnmagicToast__message {
733
+ flex: 1 1 auto;
734
+ min-width: 0;
735
+ overflow-wrap: anywhere;
736
+ }
737
+
738
+ .UnmagicToast__dismiss {
739
+ display: inline-flex;
740
+ flex-shrink: 0;
741
+ margin: -0.125rem -0.25rem -0.125rem 0;
742
+ padding: 0.25rem;
743
+ color: inherit;
744
+ background: transparent;
745
+ border: 0;
746
+ border-radius: 0.25rem;
747
+ opacity: 0.6;
748
+ cursor: pointer;
749
+ }
750
+
751
+ .UnmagicToast__dismiss:hover,
752
+ .UnmagicToast__dismiss:focus-visible {
753
+ opacity: 1;
754
+ }
755
+
756
+ .UnmagicToast__dismiss .UnmagicIcon {
757
+ width: 0.875rem;
758
+ height: 0.875rem;
759
+ }
760
+
761
+ .UnmagicToast--good {
762
+ color: var(--unmagic-good, var(--color-green-700, #15803d));
763
+ background-color: var(--unmagic-good-surface, var(--color-green-50, #f0fdf4));
764
+ border-color: var(--unmagic-good-border, var(--color-green-200, #bbf7d0));
765
+ }
766
+
767
+ .UnmagicToast--warn {
768
+ color: var(--unmagic-warn, var(--color-amber-700, #b45309));
769
+ background-color: var(--unmagic-warn-surface, var(--color-amber-50, #fffbeb));
770
+ border-color: var(--unmagic-warn-border, var(--color-amber-200, #fde68a));
771
+ }
772
+
773
+ .UnmagicToast--bad {
774
+ color: var(--unmagic-bad, var(--color-red-700, #b91c1c));
775
+ background-color: var(--unmagic-bad-surface, var(--color-red-50, #fef2f2));
776
+ border-color: var(--unmagic-bad-border, var(--color-red-200, #fecaca));
777
+ }
778
+
779
+ .UnmagicToast--info .UnmagicToast__icon {
780
+ color: var(--unmagic-text-3, var(--color-neutral-500, #737373));
781
+ }
782
+
783
+ /* ----------------------------------------------------------------- Badges */
784
+
785
+ .UnmagicBadge {
786
+ display: inline-flex;
787
+ align-items: center;
788
+ gap: 0.25rem;
789
+ padding: 0.125rem 0.5rem;
790
+ font-size: 0.75rem;
791
+ line-height: 1rem;
792
+ font-weight: 500;
793
+ white-space: nowrap;
794
+ color: var(--unmagic-text-2, var(--color-neutral-600, #525252));
795
+ background-color: var(--unmagic-surface-3, var(--color-neutral-100, #f5f5f5));
796
+ border-radius: 9999px;
797
+ }
798
+
799
+ .UnmagicBadge--good {
800
+ color: var(--unmagic-good, var(--color-green-700, #15803d));
801
+ background-color: var(--unmagic-good-surface, var(--color-green-50, #f0fdf4));
802
+ }
803
+
804
+ .UnmagicBadge--warn {
805
+ color: var(--unmagic-warn, var(--color-amber-700, #b45309));
806
+ background-color: var(--unmagic-warn-surface, var(--color-amber-50, #fffbeb));
807
+ }
808
+
809
+ .UnmagicBadge--bad {
810
+ color: var(--unmagic-bad, var(--color-red-700, #b91c1c));
811
+ background-color: var(--unmagic-bad-surface, var(--color-red-50, #fef2f2));
812
+ }
813
+
814
+ /* Neutral, but outlined, so it reads as a note rather than a state. */
815
+ .UnmagicBadge--info {
816
+ background-color: transparent;
817
+ box-shadow: inset 0 0 0 1px var(--unmagic-border-strong, var(--color-neutral-300, #d4d4d4));
818
+ }
819
+
820
+ .UnmagicBadge--accent {
821
+ color: var(--unmagic-on-accent, var(--color-white, #fff));
822
+ background-color: var(--unmagic-accent, var(--color-neutral-900, #171717));
823
+ }
824
+
825
+ /* ------------------------------------------------------------------ Cards */
826
+
827
+ /* No overflow: hidden, so a dropdown inside a card isn't clipped; the footer
828
+ rounds its own corners instead. A column whose body grows, so a card stretched
829
+ to its row's height in a grid keeps its footer on the bottom edge. */
830
+ .UnmagicCard {
831
+ display: flex;
832
+ flex-direction: column;
833
+ color: var(--unmagic-text, var(--color-neutral-900, #171717));
834
+ background-color: var(--unmagic-surface, var(--color-white, #fff));
835
+ border: 1px solid var(--unmagic-border, var(--color-neutral-200, #e5e5e5));
836
+ border-radius: 0.75rem;
837
+ }
838
+
839
+ .UnmagicCard--link {
840
+ text-decoration: none;
841
+ transition: border-color 150ms ease, box-shadow 150ms ease;
842
+ }
843
+
844
+ .UnmagicCard--link:hover {
845
+ border-color: var(--unmagic-border-strong, var(--color-neutral-300, #d4d4d4));
846
+ box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.08);
847
+ }
848
+
849
+ .UnmagicCard--link:focus-visible {
850
+ outline: 2px solid var(--unmagic-focus, var(--color-neutral-400, #a3a3a3));
851
+ outline-offset: 2px;
852
+ }
853
+
854
+ .UnmagicCard__header {
855
+ display: flex;
856
+ align-items: center;
857
+ justify-content: space-between;
858
+ gap: 0.75rem;
859
+ margin: 0;
860
+ padding: 1rem 1rem 0;
861
+ }
862
+
863
+ .UnmagicCard__title {
864
+ margin: 0;
865
+ font-size: 1rem;
866
+ line-height: 1.5rem;
867
+ font-weight: 600;
868
+ }
869
+
870
+ .UnmagicCard__actions {
871
+ display: flex;
872
+ align-items: center;
873
+ gap: 0.5rem;
874
+ margin-left: auto;
875
+ }
876
+
877
+ .UnmagicCard__body {
878
+ flex: 1 1 auto;
879
+ padding: 1rem;
880
+ font-size: 0.875rem;
881
+ line-height: 1.5;
882
+ }
883
+
884
+ .UnmagicCard__body--flush {
885
+ padding: 0;
886
+ }
887
+
888
+ .UnmagicCard__header + .UnmagicCard__body--flush {
889
+ margin-top: 1rem;
890
+ }
891
+
892
+ /* A table in a flush card borrows the card's edge rather than drawing its own. */
893
+ .UnmagicCard__body--flush > .UnmagicTable {
894
+ border: 0;
895
+ border-radius: 0;
896
+ }
897
+
898
+ .UnmagicCard__footer {
899
+ display: flex;
900
+ align-items: center;
901
+ justify-content: flex-end;
902
+ gap: 0.75rem;
903
+ padding: 0.75rem 1rem;
904
+ font-size: 0.875rem;
905
+ color: var(--unmagic-text-2, var(--color-neutral-600, #525252));
906
+ background-color: var(--unmagic-surface-2, var(--color-neutral-50, #fafafa));
907
+ border-top: 1px solid var(--unmagic-border, var(--color-neutral-200, #e5e5e5));
908
+ border-radius: 0 0 calc(0.75rem - 1px) calc(0.75rem - 1px);
909
+ }
910
+
911
+ /* ----------------------------------------------------------- Page headers */
912
+
913
+ .UnmagicPageHeader {
914
+ margin: 0 0 1.5rem;
915
+ padding: 0;
916
+ }
917
+
918
+ .UnmagicPageHeader__back {
919
+ display: inline-flex;
920
+ align-items: center;
921
+ gap: 0.25rem;
922
+ margin-bottom: 0.5rem;
923
+ font-size: 0.875rem;
924
+ color: var(--unmagic-text-3, var(--color-neutral-500, #737373));
925
+ text-decoration: none;
926
+ }
927
+
928
+ .UnmagicPageHeader__back:hover {
929
+ color: var(--unmagic-text, var(--color-neutral-900, #171717));
930
+ }
931
+
932
+ /* The title column shrinks and wraps, so a long description sits beside the
933
+ actions rather than pushing them onto a row of their own — until there's no
934
+ room, when the actions drop below. */
935
+ .UnmagicPageHeader__row {
936
+ display: flex;
937
+ flex-wrap: wrap;
938
+ align-items: flex-start;
939
+ justify-content: space-between;
940
+ gap: 1rem;
941
+ }
942
+
943
+ .UnmagicPageHeader__main {
944
+ flex: 1 1 20rem;
945
+ min-width: 0;
946
+ }
947
+
948
+ .UnmagicPageHeader__heading {
949
+ display: flex;
950
+ flex-wrap: wrap;
951
+ align-items: center;
952
+ gap: 0.75rem;
953
+ }
954
+
955
+ .UnmagicPageHeader__title {
956
+ margin: 0;
957
+ font-size: 1.5rem;
958
+ line-height: 2rem;
959
+ font-weight: 600;
960
+ letter-spacing: -0.01em;
961
+ color: var(--unmagic-text, var(--color-neutral-900, #171717));
962
+ }
963
+
964
+ .UnmagicPageHeader__description {
965
+ max-width: 42rem;
966
+ margin-top: 0.25rem;
967
+ font-size: 0.875rem;
968
+ line-height: 1.5;
969
+ color: var(--unmagic-text-3, var(--color-neutral-500, #737373));
970
+ }
971
+
972
+ .UnmagicPageHeader__actions {
973
+ display: flex;
974
+ flex-shrink: 0;
975
+ align-items: center;
976
+ gap: 0.75rem;
977
+ }
978
+
979
+ /* --------------------------------------------------------------- Callouts */
980
+
981
+ .UnmagicCallout {
982
+ display: flex;
983
+ align-items: flex-start;
984
+ gap: 0.75rem;
985
+ padding: 1rem;
986
+ font-size: 0.875rem;
987
+ line-height: 1.5;
988
+ color: var(--unmagic-text-2, var(--color-neutral-600, #525252));
989
+ border: 1px solid var(--unmagic-border, var(--color-neutral-200, #e5e5e5));
990
+ border-radius: 0.5rem;
991
+ }
992
+
993
+ .UnmagicCallout__icon {
994
+ margin-top: 0.125rem;
995
+ }
996
+
997
+ .UnmagicCallout__content {
998
+ flex: 1 1 auto;
999
+ min-width: 0;
1000
+ }
1001
+
1002
+ .UnmagicCallout__heading {
1003
+ display: flex;
1004
+ flex-wrap: wrap;
1005
+ align-items: center;
1006
+ gap: 0.5rem;
1007
+ margin-bottom: 0.25rem;
1008
+ }
1009
+
1010
+ .UnmagicCallout__title {
1011
+ margin: 0;
1012
+ font-weight: 600;
1013
+ color: var(--unmagic-text, var(--color-neutral-900, #171717));
1014
+ }
1015
+
1016
+ .UnmagicCallout__body > :first-child {
1017
+ margin-top: 0;
1018
+ }
1019
+
1020
+ .UnmagicCallout__body > :last-child {
1021
+ margin-bottom: 0;
1022
+ }
1023
+
1024
+ .UnmagicCallout--good {
1025
+ background-color: var(--unmagic-good-surface, var(--color-green-50, #f0fdf4));
1026
+ border-color: var(--unmagic-good-border, var(--color-green-200, #bbf7d0));
1027
+ }
1028
+
1029
+ .UnmagicCallout--good .UnmagicCallout__icon {
1030
+ color: var(--unmagic-good, var(--color-green-700, #15803d));
1031
+ }
1032
+
1033
+ .UnmagicCallout--warn {
1034
+ background-color: var(--unmagic-warn-surface, var(--color-amber-50, #fffbeb));
1035
+ border-color: var(--unmagic-warn-border, var(--color-amber-200, #fde68a));
1036
+ }
1037
+
1038
+ .UnmagicCallout--warn .UnmagicCallout__icon {
1039
+ color: var(--unmagic-warn, var(--color-amber-700, #b45309));
1040
+ }
1041
+
1042
+ .UnmagicCallout--bad {
1043
+ background-color: var(--unmagic-bad-surface, var(--color-red-50, #fef2f2));
1044
+ border-color: var(--unmagic-bad-border, var(--color-red-200, #fecaca));
1045
+ }
1046
+
1047
+ .UnmagicCallout--bad .UnmagicCallout__icon {
1048
+ color: var(--unmagic-bad, var(--color-red-700, #b91c1c));
1049
+ }
1050
+
1051
+ .UnmagicCallout--info {
1052
+ background-color: var(--unmagic-surface-2, var(--color-neutral-50, #fafafa));
1053
+ }
1054
+
1055
+ .UnmagicCallout--info .UnmagicCallout__icon {
1056
+ color: var(--unmagic-text-3, var(--color-neutral-500, #737373));
1057
+ }
1058
+
1059
+ /* --------------------------------------------------------------- Tooltips */
1060
+
1061
+ /* A term explains itself, so it looks like one before the script upgrades it. */
1062
+ .UnmagicTooltip--term {
1063
+ text-decoration: underline dashed;
1064
+ text-decoration-color: var(--unmagic-text-3, var(--color-neutral-500, #737373));
1065
+ text-underline-offset: 0.2em;
1066
+ cursor: help;
1067
+ }
1068
+
1069
+ .UnmagicTooltip--term:focus-visible {
1070
+ outline: 2px solid var(--unmagic-focus, var(--color-neutral-400, #a3a3a3));
1071
+ outline-offset: 2px;
1072
+ border-radius: 2px;
1073
+ }
1074
+
1075
+ /* A manual popover, placed by the element; the popover defaults are undone and
1076
+ the host's type styles reset, since the hint inherits from wherever it sits. */
1077
+ .UnmagicTooltip__popup {
1078
+ position: fixed;
1079
+ inset: auto;
1080
+ width: max-content;
1081
+ max-width: min(20rem, calc(100vw - 1rem));
1082
+ margin: 0;
1083
+ padding: 0.375rem 0.625rem;
1084
+ font-size: 0.75rem;
1085
+ line-height: 1.4;
1086
+ font-weight: 400;
1087
+ font-style: normal;
1088
+ letter-spacing: normal;
1089
+ text-align: left;
1090
+ text-transform: none;
1091
+ white-space: normal;
1092
+ color: var(--unmagic-on-tooltip, var(--color-white, #fff));
1093
+ background-color: var(--unmagic-tooltip, var(--color-neutral-900, #171717));
1094
+ border: 0;
1095
+ border-radius: 0.375rem;
1096
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
1097
+ opacity: 0;
1098
+ pointer-events: none;
1099
+ transition: opacity 100ms ease;
1100
+ }
1101
+
1102
+ .UnmagicTooltip__popup[data-open] {
1103
+ opacity: 1;
1104
+ }
1105
+
1106
+ @media (prefers-reduced-motion: reduce) {
1107
+ .UnmagicTooltip__popup {
1108
+ transition: none;
1109
+ }
1110
+ }
1111
+
1112
+ /* ------------------------------------------------------------------ Menus */
1113
+
1114
+ .UnmagicMenu {
1115
+ position: relative;
1116
+ display: inline-block;
1117
+ }
1118
+
1119
+ .UnmagicMenu__trigger {
1120
+ list-style: none;
1121
+ }
1122
+
1123
+ .UnmagicMenu__trigger::-webkit-details-marker {
1124
+ display: none;
1125
+ }
1126
+
1127
+ .UnmagicMenu__panel {
1128
+ position: absolute;
1129
+ top: calc(100% + 0.25rem);
1130
+ z-index: 50;
1131
+ display: flex;
1132
+ flex-direction: column;
1133
+ min-width: 11rem;
1134
+ padding: 0.25rem;
1135
+ background-color: var(--unmagic-surface, var(--color-white, #fff));
1136
+ border: 1px solid var(--unmagic-border, var(--color-neutral-200, #e5e5e5));
1137
+ border-radius: 0.5rem;
1138
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
1139
+ }
1140
+
1141
+ .UnmagicMenu__panel--end {
1142
+ right: 0;
1143
+ }
1144
+
1145
+ .UnmagicMenu__panel--start {
1146
+ left: 0;
1147
+ }
1148
+
1149
+ /* button_to wraps each button in a form; this keeps it out of the column's way. */
1150
+ .UnmagicMenu__form {
1151
+ display: contents;
1152
+ }
1153
+
1154
+ .UnmagicMenu__item {
1155
+ display: flex;
1156
+ align-items: center;
1157
+ gap: 0.5rem;
1158
+ width: 100%;
1159
+ padding: 0.375rem 0.625rem;
1160
+ font: inherit;
1161
+ font-size: 0.875rem;
1162
+ line-height: 1.25rem;
1163
+ text-align: left;
1164
+ text-decoration: none;
1165
+ white-space: nowrap;
1166
+ color: var(--unmagic-text, var(--color-neutral-900, #171717));
1167
+ background: transparent;
1168
+ border: 0;
1169
+ border-radius: 0.375rem;
1170
+ cursor: pointer;
1171
+ }
1172
+
1173
+ .UnmagicMenu__item:hover,
1174
+ .UnmagicMenu__item:focus-visible {
1175
+ background-color: var(--unmagic-surface-3, var(--color-neutral-100, #f5f5f5));
1176
+ outline: none;
1177
+ }
1178
+
1179
+ .UnmagicMenu__item .UnmagicIcon {
1180
+ color: var(--unmagic-text-3, var(--color-neutral-500, #737373));
1181
+ }
1182
+
1183
+ .UnmagicMenu__item--danger,
1184
+ .UnmagicMenu__item--danger .UnmagicIcon {
1185
+ color: var(--unmagic-bad, var(--color-red-600, #dc2626));
1186
+ }
1187
+
1188
+ .UnmagicMenu__item--danger:hover,
1189
+ .UnmagicMenu__item--danger:focus-visible {
1190
+ background-color: var(--unmagic-bad-surface, var(--color-red-50, #fef2f2));
1191
+ }
1192
+
1193
+ .UnmagicMenu__divider {
1194
+ margin: 0.25rem -0.25rem;
1195
+ border: 0;
1196
+ border-top: 1px solid var(--unmagic-border, var(--color-neutral-200, #e5e5e5));
1197
+ }
1198
+
1199
+ /* ------------------------------------------------------------------- Tabs */
1200
+
1201
+ /* A segmented control: an inset track with the selected tab raised out of it.
1202
+ --unmagic-raised is that tab's surface, separate from --unmagic-surface because
1203
+ a dark theme raises by lightening rather than by staying the page colour. */
1204
+ .UnmagicTabs {
1205
+ display: block;
1206
+ }
1207
+
1208
+ .UnmagicTabs__list {
1209
+ display: inline-flex;
1210
+ flex-wrap: wrap;
1211
+ gap: 0.25rem;
1212
+ padding: 0.25rem;
1213
+ background-color: var(--unmagic-surface-3, var(--color-neutral-100, #f5f5f5));
1214
+ border-radius: 0.5rem;
1215
+ }
1216
+
1217
+ /* margin is reset because link tabs sit in a <nav>, and hosts often style bare
1218
+ nav links for their page chrome. */
1219
+ .UnmagicTabs__tab {
1220
+ display: inline-flex;
1221
+ align-items: center;
1222
+ gap: 0.375rem;
1223
+ margin: 0;
1224
+ padding: 0.375rem 0.75rem;
1225
+ font: inherit;
1226
+ font-size: 0.875rem;
1227
+ line-height: 1.25rem;
1228
+ font-weight: 500;
1229
+ white-space: nowrap;
1230
+ text-decoration: none;
1231
+ color: var(--unmagic-text-3, var(--color-neutral-500, #737373));
1232
+ background: transparent;
1233
+ border: 0;
1234
+ border-radius: 0.375rem;
1235
+ cursor: pointer;
1236
+ }
1237
+
1238
+ .UnmagicTabs__tab:hover {
1239
+ color: var(--unmagic-text, var(--color-neutral-900, #171717));
1240
+ }
1241
+
1242
+ .UnmagicTabs__tab[aria-selected="true"],
1243
+ .UnmagicTabs__tab[aria-current="page"] {
1244
+ color: var(--unmagic-text, var(--color-neutral-900, #171717));
1245
+ background-color: var(--unmagic-raised, var(--unmagic-surface, var(--color-white, #fff)));
1246
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.08);
1247
+ }
1248
+
1249
+ .UnmagicTabs__tab:focus-visible,
1250
+ .UnmagicTabs__panel:focus-visible {
1251
+ outline: 2px solid var(--unmagic-focus, var(--color-neutral-400, #a3a3a3));
1252
+ outline-offset: 2px;
1253
+ }
1254
+
1255
+ .UnmagicTabs__tab[aria-disabled="true"] {
1256
+ cursor: not-allowed;
1257
+ opacity: 0.55;
1258
+ }
1259
+
1260
+ .UnmagicTabs__tab[aria-disabled="true"]:hover {
1261
+ color: var(--unmagic-text-3, var(--color-neutral-500, #737373));
1262
+ }
1263
+
1264
+ .UnmagicTabs__reason {
1265
+ font-weight: 400;
1266
+ }
1267
+
1268
+ .UnmagicTabs__panel {
1269
+ padding-top: 1rem;
1270
+ border-radius: 0.25rem;
1271
+ }
1272
+
1273
+ /* -------------------------------------------------------------- Clipboard */
1274
+
1275
+ .UnmagicClipboard {
1276
+ display: inline-flex;
1277
+ vertical-align: middle;
1278
+ }
1279
+
1280
+ .UnmagicClipboard__done {
1281
+ display: none;
1282
+ }
1283
+
1284
+ .UnmagicClipboard[data-copied] .UnmagicClipboard__idle {
1285
+ display: none;
1286
+ }
1287
+
1288
+ .UnmagicClipboard[data-copied] .UnmagicClipboard__done {
1289
+ display: block;
1290
+ }
1291
+
1292
+ .UnmagicClipboard[data-copied] .UnmagicClipboard__button {
1293
+ color: var(--unmagic-good, var(--color-green-700, #15803d));
1294
+ }
1295
+
1296
+ /* ------------------------------------------------------- Autogrow textarea */
1297
+
1298
+ /* The element sets the height, so a manual resize handle would only fight it. Give
1299
+ the textarea a max-height to cap the growth. A descendant rather than a child
1300
+ selector: Rails wraps an invalid control in a field_with_errors div, inside the
1301
+ element. */
1302
+ .UnmagicAutogrow {
1303
+ display: block;
1304
+ }
1305
+
1306
+ .UnmagicAutogrow textarea {
1307
+ resize: none;
1308
+ overflow-y: hidden;
1309
+ }