composeai 0.1.3 → 0.1.5

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/src/composer.css CHANGED
@@ -1,23 +1,30 @@
1
1
  /* ComposeAI — component styles.
2
2
  *
3
- * Plain CSS (no Tailwind required) keyed off the CSS variables every consumer
4
- * defines for their theme:
3
+ * Plain CSS, NO Tailwind required. Every element the package renders (editor
4
+ * content AND chrome — card, buttons, menus, chips, …) is styled here off a
5
+ * single set of design tokens:
5
6
  * --background, --foreground, --primary, --primary-foreground,
6
7
  * --muted, --muted-foreground, --border, --card, --card-foreground,
7
8
  * --popover, --popover-foreground, --accent, --accent-foreground,
8
9
  * --destructive, --success, --warning
9
- * Values are HSL components (e.g. `258 90% 62%`) so utilities can compose
10
- * with opacities via `hsl(var(--primary) / 0.1)`.
10
+ * Values are HSL components (e.g. `258 90% 62%`) so they compose with
11
+ * opacities via `hsl(var(--primary) / 0.1)`.
12
+ *
13
+ * The package ships a complete DEFAULT value for every token (see the
14
+ * `[data-composer-scope]` block in the CHROME section below), scoped to the
15
+ * composer subtree + its portaled popovers — so a consumer who configures
16
+ * nothing still gets a correct, opaque, themed composer. The `color` /
17
+ * `tokens` / `sx` props override per-token (they apply inline on the same
18
+ * scope element, which always beats this stylesheet).
11
19
  */
12
20
 
13
21
  /* ----------------------------------------------------------------------------
14
22
  * Composer-wide custom properties.
15
23
  *
16
- * These are READ by both the package CSS below and the Tailwind utility
17
- * classes the components use (`bg-card`, `rounded-[28px]`, etc.). They are
18
- * SET inline on the composer root by `<Composer />` itself when the consumer
19
- * passes `tokens={...}`, so they only affect that one composer instance —
20
- * never the consumer app's global theme.
24
+ * These are READ by the package CSS below (`var(--composer-radius)`, etc.).
25
+ * They are SET inline on the composer root by `<Composer />` itself when the
26
+ * consumer passes `tokens={...}`, so they only affect that one composer
27
+ * instance never the consumer app's global theme.
21
28
  *
22
29
  * --composer-radius outer card corner radius (default 28px)
23
30
  * --composer-font-size editor base font size (default 15px)
@@ -386,6 +393,10 @@
386
393
  * ------------------------------------------------------------------------- */
387
394
 
388
395
  .composer-ghost-overlay {
396
+ position: absolute;
397
+ inset: 0;
398
+ pointer-events: none;
399
+ user-select: none;
389
400
  font-size: var(--composer-font-size, 15px);
390
401
  font-family: var(--composer-font-family, inherit);
391
402
  line-height: 1.6;
@@ -418,6 +429,17 @@
418
429
  font-style: italic;
419
430
  }
420
431
 
432
+ /* Padding mirrors EditorShell's editor padding so the ghost text lines up
433
+ * with the caret. `--multiline` matches `.composer-editor--multiline`
434
+ * (0.875rem 1.25rem); `--inline` matches `.composer-editor--inline`
435
+ * (0.5rem inline). Keep these in lock-step with those rules. */
436
+ .composer-ghost-overlay--multiline {
437
+ padding: 0.875rem 1.25rem;
438
+ }
439
+ .composer-ghost-overlay--inline {
440
+ padding-inline: 0.5rem;
441
+ }
442
+
421
443
  /* Inline-layout overrides: mirror the editor's 36px row line-height so
422
444
  * the suggestion sits on the same baseline as the typed text. */
423
445
  .composer-ghost-overlay.composer-ghost-overlay--inline {
@@ -451,7 +473,12 @@
451
473
  * order, with the line-height matching the 36px row height so a single
452
474
  * line of text is perfectly centered. */
453
475
  .composer-editor.composer-editor--inline {
476
+ height: 2.25rem; /* h-9 (36px) */
454
477
  line-height: 2.25rem; /* matches h-9 / leading-9 (36px) */
478
+ padding-inline: 0.5rem;
479
+ overflow-x: auto;
480
+ overflow-y: hidden;
481
+ white-space: nowrap;
455
482
  scrollbar-width: none;
456
483
  }
457
484
  .composer-editor.composer-editor--inline::-webkit-scrollbar {
@@ -478,4 +505,1131 @@
478
505
  * showing if a consumer renders one manually inside an inline composer. */
479
506
  [data-composer-root][data-composer-inline] [data-mermaid-tile] {
480
507
  display: none;
508
+ }
509
+
510
+ /* ============================================================================
511
+ * CHROME — self-contained component styles.
512
+ *
513
+ * Everything below styles the composer's *chrome* (card, toolbar, buttons,
514
+ * menus, attachment chips, tooltips, …) in plain CSS, so the package renders
515
+ * correctly with NO Tailwind in the consumer app and NO assumptions about the
516
+ * consumer's design-token names. Each element carries a semantic
517
+ * `composer-*` class (set by the components) that these rules target.
518
+ *
519
+ * Colors are keyed off the same `hsl(var(--token))` design tokens the editor
520
+ * content above uses. The package now ships a complete DEFAULT value for every
521
+ * token (below), scoped to the composer subtree + its portaled popovers, so a
522
+ * consumer who defines nothing still gets a correct, opaque, rounded composer.
523
+ * The `color` / `tokens` / `sx` props still win — they are applied inline on
524
+ * the same scope element, and inline always beats this stylesheet.
525
+ * ========================================================================= */
526
+
527
+ /* ----------------------------------------------------------------------------
528
+ * Built-in default theme.
529
+ *
530
+ * Set DIRECTLY on the scope elements (not on the consumer's `:root`) so they
531
+ * shadow whatever the host app defines globally — even when the host uses an
532
+ * incompatible format (hex, rgb, oklch) for the same variable name. Because
533
+ * they sit on the same element the `color`/`tokens` props target inline, the
534
+ * consumer's theming still overrides them per-token. `--composer-shadow-*` are
535
+ * package-private so they can't clash with a host's shadow tokens.
536
+ * ------------------------------------------------------------------------- */
537
+ .composer-root,
538
+ [data-composer-scope] {
539
+ --background: 0 0% 100%;
540
+ --foreground: 222 18% 18%;
541
+ --card: 0 0% 100%;
542
+ --card-foreground: 222 18% 18%;
543
+ --popover: 0 0% 100%;
544
+ --popover-foreground: 222 18% 18%;
545
+ --muted: 220 14% 96%;
546
+ --muted-foreground: 220 9% 46%;
547
+ --border: 220 13% 90%;
548
+ --input: 220 13% 90%;
549
+ --primary: 222 47% 11%;
550
+ --primary-foreground: 210 20% 98%;
551
+ --accent: 220 14% 95%;
552
+ --accent-foreground: 222 47% 11%;
553
+ --ring: 222 47% 11%;
554
+ --destructive: 0 72% 51%;
555
+ --destructive-foreground: 0 0% 100%;
556
+ --success: 142 71% 45%;
557
+ --success-foreground: 0 0% 100%;
558
+ --warning: 38 92% 50%;
559
+ --warning-foreground: 24 10% 10%;
560
+
561
+ --composer-shadow-soft: 0 1px 2px hsl(220 13% 10% / 0.05),
562
+ 0 12px 32px -16px hsl(220 13% 10% / 0.24);
563
+ --composer-shadow-pop: 0 4px 12px hsl(220 13% 10% / 0.08),
564
+ 0 20px 48px -20px hsl(220 13% 10% / 0.4);
565
+ }
566
+
567
+ /* Auto dark theme for hosts whose users prefer dark — keeps the composer
568
+ * readable out of the box on a dark canvas. A consumer who wants an explicit
569
+ * theme regardless of OS setting passes `tokens` (which wins inline). */
570
+ @media (prefers-color-scheme: dark) {
571
+ .composer-root,
572
+ [data-composer-scope] {
573
+ --background: 222 16% 11%;
574
+ --foreground: 210 20% 92%;
575
+ --card: 222 16% 13%;
576
+ --card-foreground: 210 20% 92%;
577
+ --popover: 222 16% 15%;
578
+ --popover-foreground: 210 20% 92%;
579
+ --muted: 220 14% 20%;
580
+ --muted-foreground: 220 9% 64%;
581
+ --border: 220 13% 26%;
582
+ --input: 220 13% 26%;
583
+ --primary: 210 20% 96%;
584
+ --primary-foreground: 222 47% 11%;
585
+ --accent: 220 13% 24%;
586
+ --accent-foreground: 210 20% 96%;
587
+ --ring: 210 20% 80%;
588
+ --destructive: 0 72% 55%;
589
+ --destructive-foreground: 0 0% 100%;
590
+ --success: 142 64% 47%;
591
+ --success-foreground: 0 0% 100%;
592
+ --warning: 38 92% 55%;
593
+ --warning-foreground: 24 10% 10%;
594
+
595
+ --composer-shadow-soft: 0 1px 2px hsl(0 0% 0% / 0.4),
596
+ 0 12px 32px -16px hsl(0 0% 0% / 0.6);
597
+ --composer-shadow-pop: 0 4px 12px hsl(0 0% 0% / 0.5),
598
+ 0 20px 48px -20px hsl(0 0% 0% / 0.7);
599
+ }
600
+ }
601
+
602
+ /* Shared keyframes / animation helpers. `composer-popover-in` already exists
603
+ * above for the small attachment popover; these cover the typeahead menus,
604
+ * spinners and pulsing recording indicators. */
605
+ @keyframes composer-slide-up {
606
+ from {
607
+ opacity: 0;
608
+ transform: translateY(-4px) scale(0.985);
609
+ }
610
+ to {
611
+ opacity: 1;
612
+ transform: none;
613
+ }
614
+ }
615
+ @keyframes composer-spin {
616
+ to {
617
+ transform: rotate(360deg);
618
+ }
619
+ }
620
+ @keyframes composer-pulse {
621
+ 50% {
622
+ opacity: 0.45;
623
+ }
624
+ }
625
+ .composer-spin {
626
+ animation: composer-spin 0.9s linear infinite;
627
+ }
628
+ .composer-pulse {
629
+ animation: composer-pulse 1.4s ease-in-out infinite;
630
+ }
631
+
632
+ /* Shared thin scrollbar (matches the editor's own, defined above). */
633
+ .composer-scroll-y {
634
+ overflow-y: auto;
635
+ scrollbar-width: thin;
636
+ scrollbar-color: hsl(var(--border)) transparent;
637
+ }
638
+ .composer-scroll-x {
639
+ overflow-x: auto;
640
+ scrollbar-width: thin;
641
+ scrollbar-color: hsl(var(--border)) transparent;
642
+ }
643
+ .composer-scroll-y::-webkit-scrollbar,
644
+ .composer-scroll-x::-webkit-scrollbar {
645
+ width: 6px;
646
+ height: 6px;
647
+ }
648
+ .composer-scroll-y::-webkit-scrollbar-thumb,
649
+ .composer-scroll-x::-webkit-scrollbar-thumb {
650
+ background: hsl(var(--border));
651
+ border-radius: 9999px;
652
+ }
653
+
654
+ /* All package SVGs inherit the current text color and never shrink in a flex
655
+ * row. Concrete sizes are set per-context below. Mermaid-rendered SVGs live
656
+ * inside `.composer-mermaid-svg` and are sized there, never by this rule. */
657
+ .composer-icon {
658
+ flex: none;
659
+ color: currentColor;
660
+ }
661
+
662
+ /* Visually-hidden but screen-reader-accessible (replaces Tailwind `sr-only`). */
663
+ .composer-sr-only {
664
+ position: absolute;
665
+ width: 1px;
666
+ height: 1px;
667
+ padding: 0;
668
+ margin: -1px;
669
+ overflow: hidden;
670
+ clip: rect(0, 0, 0, 0);
671
+ white-space: nowrap;
672
+ border: 0;
673
+ }
674
+
675
+ /* ----------------------------------------------------------------------------
676
+ * Root + card + focus/drag overlays.
677
+ * ------------------------------------------------------------------------- */
678
+ .composer-root {
679
+ display: flex;
680
+ flex-direction: column;
681
+ gap: 0.5rem;
682
+ }
683
+
684
+ [data-composer-root] {
685
+ position: relative;
686
+ border: 1px solid hsl(var(--border));
687
+ background: hsl(var(--card));
688
+ color: hsl(var(--card-foreground));
689
+ box-shadow: var(--composer-shadow-soft);
690
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
691
+ }
692
+ [data-composer-root]:focus-within {
693
+ border-color: hsl(var(--primary) / 0.4);
694
+ box-shadow: 0 0 0 1px hsl(var(--primary) / 0.15),
695
+ 0 10px 34px -14px hsl(var(--primary) / 0.35);
696
+ }
697
+ [data-composer-root][data-composer-web] {
698
+ box-shadow: 0 0 0 1px hsl(var(--primary) / 0.2), var(--composer-shadow-soft);
699
+ }
700
+ [data-composer-root][data-composer-dragging] {
701
+ border-color: hsl(var(--primary) / 0.6);
702
+ box-shadow: 0 0 0 2px hsl(var(--primary) / 0.6), var(--composer-shadow-soft);
703
+ }
704
+
705
+ .composer-overlay-glow {
706
+ pointer-events: none;
707
+ position: absolute;
708
+ inset: 0;
709
+ opacity: 0;
710
+ transition: opacity 0.25s ease;
711
+ }
712
+ [data-composer-root]:focus-within .composer-overlay-glow {
713
+ opacity: 1;
714
+ }
715
+ .composer-overlay-drop {
716
+ pointer-events: none;
717
+ position: absolute;
718
+ inset: 0;
719
+ z-index: 10;
720
+ display: flex;
721
+ align-items: center;
722
+ justify-content: center;
723
+ background: hsl(var(--primary) / 0.05);
724
+ font-size: 0.875rem;
725
+ font-weight: 500;
726
+ color: hsl(var(--primary));
727
+ backdrop-filter: blur(1px);
728
+ }
729
+
730
+ /* ----------------------------------------------------------------------------
731
+ * Editor shell layout (multiline + inline).
732
+ * ------------------------------------------------------------------------- */
733
+ .composer-editor--multiline {
734
+ min-height: 44px;
735
+ max-height: 14rem;
736
+ overflow-y: auto;
737
+ scrollbar-width: thin;
738
+ scrollbar-color: hsl(var(--border)) transparent;
739
+ padding: 0.875rem 1.25rem;
740
+ }
741
+ .composer-editor--multiline::-webkit-scrollbar {
742
+ width: 6px;
743
+ }
744
+ .composer-editor--multiline::-webkit-scrollbar-thumb {
745
+ background: hsl(var(--border));
746
+ border-radius: 9999px;
747
+ }
748
+
749
+ .composer-editor-block {
750
+ position: relative;
751
+ min-width: 0;
752
+ }
753
+ .composer-editor-block--inline {
754
+ flex: 1 1 0%;
755
+ }
756
+
757
+ .composer-placeholder {
758
+ pointer-events: none;
759
+ position: absolute;
760
+ user-select: none;
761
+ color: hsl(var(--muted-foreground));
762
+ font-size: 15px;
763
+ }
764
+ .composer-placeholder--multiline {
765
+ inset-inline: 0;
766
+ top: 0;
767
+ padding: 0.875rem 1.25rem;
768
+ line-height: 1.625;
769
+ }
770
+ .composer-placeholder--inline {
771
+ inset: 0;
772
+ padding-inline: 0.5rem;
773
+ line-height: 2.25rem;
774
+ white-space: nowrap;
775
+ overflow: hidden;
776
+ text-overflow: ellipsis;
777
+ }
778
+
779
+ .composer-inline-row {
780
+ display: flex;
781
+ align-items: center;
782
+ gap: 0.25rem;
783
+ padding: 0.375rem 0.5rem;
784
+ }
785
+ .composer-inline-toolbar,
786
+ .composer-inline-send {
787
+ display: flex;
788
+ flex: none;
789
+ align-items: center;
790
+ }
791
+
792
+ .composer-toolbar-row {
793
+ display: flex;
794
+ align-items: center;
795
+ justify-content: space-between;
796
+ padding: 0 0.75rem 0.625rem;
797
+ }
798
+
799
+ /* ----------------------------------------------------------------------------
800
+ * Toolbar + buttons.
801
+ * ------------------------------------------------------------------------- */
802
+ .composer-toolbar {
803
+ display: flex;
804
+ align-items: center;
805
+ gap: 0.25rem;
806
+ }
807
+
808
+ .composer-toolbar-btn {
809
+ display: inline-flex;
810
+ align-items: center;
811
+ justify-content: center;
812
+ height: 2rem;
813
+ width: 2rem;
814
+ border-radius: 9999px;
815
+ color: hsl(var(--muted-foreground));
816
+ background: transparent;
817
+ border: none;
818
+ cursor: pointer;
819
+ transition: background-color 0.15s ease, color 0.15s ease;
820
+ }
821
+ .composer-toolbar-btn:hover {
822
+ background: hsl(var(--accent));
823
+ color: hsl(var(--foreground));
824
+ }
825
+ .composer-toolbar-btn svg {
826
+ height: 1rem;
827
+ width: 1rem;
828
+ }
829
+
830
+ /* Web-search pill — wider, pressed state lights up in the brand color. */
831
+ .composer-web-btn {
832
+ margin-inline-start: 0.125rem;
833
+ display: inline-flex;
834
+ align-items: center;
835
+ gap: 0.375rem;
836
+ height: 2rem;
837
+ padding-inline: 0.625rem;
838
+ border-radius: 9999px;
839
+ border: none;
840
+ background: transparent;
841
+ font-size: 0.75rem;
842
+ font-weight: 500;
843
+ color: hsl(var(--muted-foreground));
844
+ cursor: pointer;
845
+ transition: background-color 0.15s ease, color 0.15s ease;
846
+ }
847
+ .composer-web-btn:hover {
848
+ background: hsl(var(--accent));
849
+ color: hsl(var(--foreground));
850
+ }
851
+ .composer-web-btn[aria-pressed="true"] {
852
+ background: hsl(var(--primary) / 0.1);
853
+ color: hsl(var(--primary));
854
+ }
855
+ .composer-web-btn svg {
856
+ height: 0.875rem;
857
+ width: 0.875rem;
858
+ }
859
+
860
+ /* Voice button + live recording timer. */
861
+ .composer-voice {
862
+ display: flex;
863
+ align-items: center;
864
+ gap: 0.375rem;
865
+ }
866
+ .composer-voice-btn[aria-pressed="true"] {
867
+ background: hsl(var(--destructive) / 0.1);
868
+ color: hsl(var(--destructive));
869
+ box-shadow: 0 0 0 1px hsl(var(--destructive) / 0.4);
870
+ }
871
+ .composer-voice-btn[aria-pressed="true"]:hover {
872
+ background: hsl(var(--destructive) / 0.15);
873
+ color: hsl(var(--destructive));
874
+ }
875
+ .composer-voice-timer {
876
+ display: flex;
877
+ align-items: center;
878
+ gap: 0.25rem;
879
+ border-radius: 9999px;
880
+ background: hsl(var(--destructive) / 0.1);
881
+ padding: 0.125rem 0.5rem;
882
+ font-size: 11px;
883
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
884
+ font-weight: 500;
885
+ font-variant-numeric: tabular-nums;
886
+ color: hsl(var(--destructive));
887
+ }
888
+ .composer-voice-dot {
889
+ height: 0.375rem;
890
+ width: 0.375rem;
891
+ border-radius: 9999px;
892
+ background: hsl(var(--destructive));
893
+ }
894
+
895
+ /* Send / stop button. Filled, circular, lifts on hover. The disabled send
896
+ * state drops to a muted fill so it reads as inert. */
897
+ .composer-send-btn {
898
+ display: inline-flex;
899
+ align-items: center;
900
+ justify-content: center;
901
+ height: 2.25rem;
902
+ width: 2.25rem;
903
+ border-radius: 9999px;
904
+ border: none;
905
+ background: hsl(var(--foreground));
906
+ color: hsl(var(--background));
907
+ box-shadow: 0 1px 2px hsl(220 13% 10% / 0.12);
908
+ cursor: pointer;
909
+ transition: transform 0.15s ease, background-color 0.15s ease,
910
+ color 0.15s ease;
911
+ }
912
+ .composer-send-btn:hover:not(:disabled) {
913
+ transform: scale(1.05);
914
+ }
915
+ .composer-send-btn:disabled {
916
+ background: hsl(var(--muted));
917
+ color: hsl(var(--muted-foreground) / 0.6);
918
+ box-shadow: none;
919
+ cursor: default;
920
+ }
921
+ .composer-send-btn svg {
922
+ height: 1rem;
923
+ width: 1rem;
924
+ }
925
+ .composer-send-btn--stop svg {
926
+ height: 0.875rem;
927
+ width: 0.875rem;
928
+ fill: currentColor;
929
+ }
930
+
931
+ /* ----------------------------------------------------------------------------
932
+ * Typeahead menus (mentions / slash) + attachment-type popover.
933
+ *
934
+ * The mention/slash menus are portaled and positioned by SmartPopover (fixed,
935
+ * via inline styles), so the class only owns the surface + animation. The
936
+ * attachment-type picker positions itself (absolute, opens upward).
937
+ * ------------------------------------------------------------------------- */
938
+ .composer-menu {
939
+ z-index: 50;
940
+ width: 16rem;
941
+ overflow: hidden;
942
+ border-radius: 0.75rem;
943
+ border: 1px solid hsl(var(--border));
944
+ background: hsl(var(--popover));
945
+ color: hsl(var(--popover-foreground));
946
+ box-shadow: var(--composer-shadow-pop);
947
+ transform-origin: top;
948
+ animation: composer-slide-up 0.16s ease-out;
949
+ }
950
+ .composer-menu--slash {
951
+ width: 18rem;
952
+ }
953
+
954
+ .composer-menu-list {
955
+ max-height: 18rem;
956
+ overflow-y: auto;
957
+ scrollbar-width: thin;
958
+ scrollbar-color: hsl(var(--border)) transparent;
959
+ padding-block: 0.25rem;
960
+ }
961
+ .composer-menu-list::-webkit-scrollbar {
962
+ width: 6px;
963
+ }
964
+ .composer-menu-list::-webkit-scrollbar-thumb {
965
+ background: hsl(var(--border));
966
+ border-radius: 9999px;
967
+ }
968
+
969
+ .composer-menu-group {
970
+ padding: 0.5rem 0.75rem 0.25rem;
971
+ font-size: 10px;
972
+ font-weight: 500;
973
+ text-transform: uppercase;
974
+ letter-spacing: 0.05em;
975
+ color: hsl(var(--muted-foreground));
976
+ }
977
+
978
+ .composer-menu-item {
979
+ display: flex;
980
+ cursor: pointer;
981
+ align-items: center;
982
+ gap: 0.625rem;
983
+ padding: 0.375rem 0.625rem;
984
+ font-size: 0.875rem;
985
+ color: hsl(var(--foreground));
986
+ }
987
+ .composer-menu-item[aria-selected="true"],
988
+ .composer-menu-item[data-active] {
989
+ background: hsl(var(--accent));
990
+ color: hsl(var(--accent-foreground));
991
+ }
992
+
993
+ .composer-menu-text {
994
+ display: flex;
995
+ min-width: 0;
996
+ flex-direction: column;
997
+ line-height: 1.2;
998
+ }
999
+ .composer-menu-label {
1000
+ overflow: hidden;
1001
+ text-overflow: ellipsis;
1002
+ white-space: nowrap;
1003
+ font-weight: 500;
1004
+ }
1005
+ .composer-menu-desc {
1006
+ overflow: hidden;
1007
+ text-overflow: ellipsis;
1008
+ white-space: nowrap;
1009
+ font-size: 11px;
1010
+ color: hsl(var(--muted-foreground));
1011
+ }
1012
+ .composer-menu-shortcut {
1013
+ margin-inline-start: auto;
1014
+ border-radius: 0.25rem;
1015
+ border: 1px solid hsl(var(--border));
1016
+ background: hsl(var(--muted));
1017
+ padding: 0.125rem 0.375rem;
1018
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1019
+ font-size: 10px;
1020
+ color: hsl(var(--muted-foreground));
1021
+ }
1022
+
1023
+ /* Round avatar bubble for mention rows (icon / initial fallback). */
1024
+ .composer-menu-avatar {
1025
+ display: flex;
1026
+ height: 1.75rem;
1027
+ width: 1.75rem;
1028
+ flex: none;
1029
+ align-items: center;
1030
+ justify-content: center;
1031
+ border-radius: 9999px;
1032
+ background: hsl(var(--primary) / 0.1);
1033
+ color: hsl(var(--primary));
1034
+ font-size: 0.75rem;
1035
+ font-weight: 600;
1036
+ }
1037
+ /* Square-ish badge for slash-command icons. */
1038
+ .composer-menu-icon {
1039
+ display: flex;
1040
+ height: 1.75rem;
1041
+ width: 1.75rem;
1042
+ flex: none;
1043
+ align-items: center;
1044
+ justify-content: center;
1045
+ border-radius: 0.5rem;
1046
+ background: hsl(var(--muted));
1047
+ color: hsl(var(--muted-foreground));
1048
+ }
1049
+
1050
+ /* Skeleton shimmer rows shown while async items load. */
1051
+ .composer-skel-row {
1052
+ padding: 0.375rem 0.625rem;
1053
+ }
1054
+ .composer-skel-line {
1055
+ display: flex;
1056
+ align-items: center;
1057
+ gap: 0.625rem;
1058
+ padding: 0.375rem 0;
1059
+ }
1060
+ .composer-skel-avatar {
1061
+ height: 1.75rem;
1062
+ width: 1.75rem;
1063
+ flex: none;
1064
+ border-radius: 9999px;
1065
+ background: hsl(var(--muted));
1066
+ }
1067
+ .composer-skel-avatar--square {
1068
+ border-radius: 0.5rem;
1069
+ }
1070
+ .composer-skel-text {
1071
+ display: flex;
1072
+ min-width: 0;
1073
+ flex: 1;
1074
+ flex-direction: column;
1075
+ gap: 0.375rem;
1076
+ }
1077
+ .composer-skel-bar {
1078
+ height: 0.625rem;
1079
+ border-radius: 0.25rem;
1080
+ background: hsl(var(--muted));
1081
+ }
1082
+ .composer-skel-bar--sm {
1083
+ height: 0.5rem;
1084
+ background: hsl(var(--muted) / 0.7);
1085
+ }
1086
+ .composer-skel-group {
1087
+ display: flex;
1088
+ flex-direction: column;
1089
+ gap: 0.25rem;
1090
+ }
1091
+ .composer-skel-grouplabel {
1092
+ display: block;
1093
+ height: 0.5rem;
1094
+ width: 4rem;
1095
+ border-radius: 0.25rem;
1096
+ background: hsl(var(--muted) / 0.7);
1097
+ margin-block: 0.125rem 0.375rem;
1098
+ }
1099
+
1100
+ /* Internal Avatar (image with initials fallback) used in mention rows. */
1101
+ .composer-avatar {
1102
+ display: inline-flex;
1103
+ flex: none;
1104
+ user-select: none;
1105
+ align-items: center;
1106
+ justify-content: center;
1107
+ overflow: hidden;
1108
+ border-radius: 9999px;
1109
+ background: hsl(var(--primary) / 0.1);
1110
+ color: hsl(var(--primary));
1111
+ font-size: 0.75rem;
1112
+ font-weight: 600;
1113
+ }
1114
+ .composer-avatar-img {
1115
+ height: 100%;
1116
+ width: 100%;
1117
+ object-fit: cover;
1118
+ }
1119
+
1120
+ /* Attachment-type picker popover — anchored above the paperclip. */
1121
+ .composer-attach-picker {
1122
+ position: relative;
1123
+ }
1124
+ .composer-attach-menu {
1125
+ position: absolute;
1126
+ bottom: 100%;
1127
+ inset-inline-start: 0;
1128
+ z-index: 30;
1129
+ margin-bottom: 0.5rem;
1130
+ min-width: 200px;
1131
+ overflow: hidden;
1132
+ border-radius: 0.75rem;
1133
+ border: 1px solid hsl(var(--border));
1134
+ background: hsl(var(--popover));
1135
+ color: hsl(var(--popover-foreground));
1136
+ padding: 0.25rem;
1137
+ box-shadow: var(--composer-shadow-pop);
1138
+ }
1139
+ .composer-attach-item {
1140
+ display: flex;
1141
+ width: 100%;
1142
+ align-items: center;
1143
+ gap: 0.625rem;
1144
+ border-radius: 0.5rem;
1145
+ border: none;
1146
+ background: transparent;
1147
+ padding: 0.375rem 0.625rem;
1148
+ text-align: start;
1149
+ font-size: 0.875rem;
1150
+ color: hsl(var(--foreground));
1151
+ cursor: pointer;
1152
+ transition: background-color 0.15s ease, color 0.15s ease;
1153
+ }
1154
+ .composer-attach-item:hover {
1155
+ background: hsl(var(--accent) / 0.6);
1156
+ }
1157
+ .composer-attach-item[data-active] {
1158
+ background: hsl(var(--accent));
1159
+ color: hsl(var(--accent-foreground));
1160
+ }
1161
+ .composer-attach-item-icon {
1162
+ display: flex;
1163
+ height: 1.25rem;
1164
+ width: 1.25rem;
1165
+ flex: none;
1166
+ align-items: center;
1167
+ justify-content: center;
1168
+ color: hsl(var(--muted-foreground));
1169
+ }
1170
+ .composer-attach-item-label {
1171
+ min-width: 0;
1172
+ flex: 1;
1173
+ overflow: hidden;
1174
+ text-overflow: ellipsis;
1175
+ white-space: nowrap;
1176
+ font-weight: 500;
1177
+ }
1178
+ .composer-attach-item-desc {
1179
+ flex: none;
1180
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1181
+ font-size: 11px;
1182
+ color: hsl(var(--muted-foreground));
1183
+ }
1184
+
1185
+ /* ----------------------------------------------------------------------------
1186
+ * Attachment tray + chips.
1187
+ * ------------------------------------------------------------------------- */
1188
+ .composer-attachment-tray {
1189
+ display: flex;
1190
+ flex-wrap: wrap;
1191
+ gap: 0.5rem;
1192
+ padding: 0.75rem 1rem 0;
1193
+ }
1194
+
1195
+ .composer-chip {
1196
+ position: relative;
1197
+ }
1198
+ .composer-chip--image {
1199
+ height: 4rem;
1200
+ width: 4rem;
1201
+ overflow: hidden;
1202
+ border-radius: 0.75rem;
1203
+ border: 1px solid hsl(var(--border));
1204
+ background: hsl(var(--muted));
1205
+ }
1206
+ .composer-chip-img {
1207
+ height: 100%;
1208
+ width: 100%;
1209
+ object-fit: cover;
1210
+ }
1211
+ .composer-chip-overlay {
1212
+ position: absolute;
1213
+ inset: 0;
1214
+ display: grid;
1215
+ place-items: center;
1216
+ }
1217
+ .composer-chip-overlay--uploading {
1218
+ background: hsl(var(--foreground) / 0.5);
1219
+ color: hsl(var(--background));
1220
+ }
1221
+ .composer-chip-overlay--failed {
1222
+ background: hsl(var(--destructive) / 0.55);
1223
+ color: hsl(var(--destructive-foreground));
1224
+ }
1225
+ .composer-chip-overlay svg {
1226
+ height: 1.25rem;
1227
+ width: 1.25rem;
1228
+ }
1229
+ .composer-chip-zoom {
1230
+ position: absolute;
1231
+ inset: 0;
1232
+ display: flex;
1233
+ align-items: center;
1234
+ justify-content: center;
1235
+ border: none;
1236
+ background: hsl(var(--foreground) / 0.4);
1237
+ color: hsl(var(--background));
1238
+ opacity: 0;
1239
+ cursor: pointer;
1240
+ transition: opacity 0.15s ease;
1241
+ }
1242
+ .composer-chip--image:hover .composer-chip-zoom {
1243
+ opacity: 1;
1244
+ }
1245
+ .composer-chip-zoom svg {
1246
+ height: 1rem;
1247
+ width: 1rem;
1248
+ }
1249
+ .composer-chip-remove {
1250
+ position: absolute;
1251
+ inset-inline-end: 0.25rem;
1252
+ top: 0.25rem;
1253
+ z-index: 10;
1254
+ display: flex;
1255
+ height: 1.25rem;
1256
+ width: 1.25rem;
1257
+ align-items: center;
1258
+ justify-content: center;
1259
+ border: none;
1260
+ border-radius: 9999px;
1261
+ background: hsl(var(--foreground));
1262
+ color: hsl(var(--background));
1263
+ cursor: pointer;
1264
+ opacity: 0;
1265
+ transition: opacity 0.15s ease;
1266
+ }
1267
+ .composer-chip--image:hover .composer-chip-remove,
1268
+ .composer-chip-remove[data-visible] {
1269
+ opacity: 1;
1270
+ }
1271
+ .composer-chip-remove svg {
1272
+ height: 0.75rem;
1273
+ width: 0.75rem;
1274
+ }
1275
+
1276
+ .composer-chip--file {
1277
+ display: flex;
1278
+ align-items: center;
1279
+ gap: 0.5rem;
1280
+ border-radius: 0.75rem;
1281
+ border: 1px solid hsl(var(--border));
1282
+ background: hsl(var(--card));
1283
+ padding: 0.375rem 0.25rem 0.375rem 0.5rem;
1284
+ }
1285
+ .composer-chip--file[data-failed] {
1286
+ border-color: hsl(var(--destructive) / 0.6);
1287
+ }
1288
+ .composer-chip-icon {
1289
+ display: flex;
1290
+ height: 2rem;
1291
+ width: 2rem;
1292
+ flex: none;
1293
+ align-items: center;
1294
+ justify-content: center;
1295
+ border-radius: 0.5rem;
1296
+ background: hsl(var(--muted));
1297
+ color: hsl(var(--muted-foreground));
1298
+ }
1299
+ .composer-chip-icon[data-failed] {
1300
+ background: hsl(var(--destructive) / 0.15);
1301
+ color: hsl(var(--destructive));
1302
+ }
1303
+ .composer-chip-icon svg {
1304
+ height: 1rem;
1305
+ width: 1rem;
1306
+ }
1307
+ .composer-chip-text {
1308
+ display: flex;
1309
+ min-width: 0;
1310
+ flex-direction: column;
1311
+ }
1312
+ .composer-chip-name {
1313
+ max-width: 160px;
1314
+ overflow: hidden;
1315
+ text-overflow: ellipsis;
1316
+ white-space: nowrap;
1317
+ font-size: 0.75rem;
1318
+ font-weight: 500;
1319
+ line-height: 1.2;
1320
+ }
1321
+ .composer-chip-meta {
1322
+ font-size: 10px;
1323
+ color: hsl(var(--muted-foreground));
1324
+ }
1325
+ .composer-chip-meta[data-failed] {
1326
+ color: hsl(var(--destructive));
1327
+ }
1328
+ .composer-chip-remove-inline {
1329
+ display: flex;
1330
+ height: 1.5rem;
1331
+ width: 1.5rem;
1332
+ flex: none;
1333
+ align-items: center;
1334
+ justify-content: center;
1335
+ border: none;
1336
+ border-radius: 9999px;
1337
+ background: transparent;
1338
+ color: hsl(var(--muted-foreground));
1339
+ cursor: pointer;
1340
+ transition: background-color 0.15s ease, color 0.15s ease;
1341
+ }
1342
+ .composer-chip-remove-inline:hover {
1343
+ background: hsl(var(--accent));
1344
+ color: hsl(var(--foreground));
1345
+ }
1346
+ .composer-chip-remove-inline svg {
1347
+ height: 0.875rem;
1348
+ width: 0.875rem;
1349
+ }
1350
+
1351
+ /* ----------------------------------------------------------------------------
1352
+ * Image lightbox (portaled, full-viewport).
1353
+ * ------------------------------------------------------------------------- */
1354
+ .composer-lightbox {
1355
+ position: fixed;
1356
+ inset: 0;
1357
+ z-index: 50;
1358
+ display: flex;
1359
+ align-items: center;
1360
+ justify-content: center;
1361
+ padding: 1.5rem;
1362
+ }
1363
+ .composer-lightbox-backdrop {
1364
+ position: absolute;
1365
+ inset: 0;
1366
+ background: hsl(var(--foreground) / 0.7);
1367
+ backdrop-filter: blur(4px);
1368
+ }
1369
+ .composer-lightbox-close {
1370
+ position: absolute;
1371
+ inset-inline-end: 1.25rem;
1372
+ top: 1.25rem;
1373
+ display: flex;
1374
+ height: 2.25rem;
1375
+ width: 2.25rem;
1376
+ align-items: center;
1377
+ justify-content: center;
1378
+ border: none;
1379
+ border-radius: 9999px;
1380
+ background: hsl(var(--card));
1381
+ color: hsl(var(--foreground));
1382
+ box-shadow: var(--composer-shadow-soft);
1383
+ cursor: pointer;
1384
+ transition: background-color 0.15s ease;
1385
+ }
1386
+ .composer-lightbox-close:hover {
1387
+ background: hsl(var(--accent));
1388
+ }
1389
+ .composer-lightbox-close svg {
1390
+ height: 1rem;
1391
+ width: 1rem;
1392
+ }
1393
+ .composer-lightbox-img {
1394
+ position: relative;
1395
+ max-height: 85vh;
1396
+ max-width: 85vw;
1397
+ border-radius: 0.5rem;
1398
+ object-fit: contain;
1399
+ box-shadow: 0 25px 50px -12px hsl(0 0% 0% / 0.5);
1400
+ }
1401
+
1402
+ /* ----------------------------------------------------------------------------
1403
+ * Mermaid diagram preview.
1404
+ * ------------------------------------------------------------------------- */
1405
+ .composer-mermaid {
1406
+ border-top: 1px solid hsl(var(--border) / 0.6);
1407
+ background: hsl(var(--muted) / 0.3);
1408
+ padding: 0.75rem 1rem;
1409
+ }
1410
+ .composer-mermaid-head {
1411
+ display: flex;
1412
+ align-items: center;
1413
+ gap: 0.375rem;
1414
+ margin-bottom: 0.375rem;
1415
+ font-size: 10px;
1416
+ font-weight: 500;
1417
+ text-transform: uppercase;
1418
+ letter-spacing: 0.05em;
1419
+ color: hsl(var(--muted-foreground));
1420
+ }
1421
+ .composer-mermaid-head svg {
1422
+ height: 0.75rem;
1423
+ width: 0.75rem;
1424
+ }
1425
+ .composer-mermaid-row {
1426
+ display: flex;
1427
+ gap: 0.5rem;
1428
+ overflow-x: auto;
1429
+ scrollbar-width: thin;
1430
+ scrollbar-color: hsl(var(--border)) transparent;
1431
+ }
1432
+ .composer-mermaid-tile {
1433
+ position: relative;
1434
+ flex: none;
1435
+ overflow: hidden;
1436
+ border-radius: 0.5rem;
1437
+ border: 1px solid hsl(var(--border));
1438
+ background: hsl(var(--card));
1439
+ padding: 0;
1440
+ cursor: pointer;
1441
+ transition: border-color 0.15s ease;
1442
+ }
1443
+ .composer-mermaid-tile:hover {
1444
+ border-color: hsl(var(--primary) / 0.4);
1445
+ }
1446
+ .composer-mermaid-svg {
1447
+ height: 6rem;
1448
+ width: 10rem;
1449
+ }
1450
+ .composer-mermaid-svg svg {
1451
+ height: 100%;
1452
+ width: 100%;
1453
+ }
1454
+ .composer-mermaid-zoom {
1455
+ position: absolute;
1456
+ inset-inline-end: 0.25rem;
1457
+ top: 0.25rem;
1458
+ display: flex;
1459
+ height: 1.25rem;
1460
+ width: 1.25rem;
1461
+ align-items: center;
1462
+ justify-content: center;
1463
+ border-radius: 9999px;
1464
+ background: hsl(var(--foreground) / 0.7);
1465
+ color: hsl(var(--background));
1466
+ opacity: 0;
1467
+ transition: opacity 0.15s ease;
1468
+ }
1469
+ .composer-mermaid-tile:hover .composer-mermaid-zoom {
1470
+ opacity: 1;
1471
+ }
1472
+ .composer-mermaid-zoom svg {
1473
+ height: 0.75rem;
1474
+ width: 0.75rem;
1475
+ }
1476
+ .composer-mermaid-msg {
1477
+ display: grid;
1478
+ height: 6rem;
1479
+ width: 10rem;
1480
+ place-items: center;
1481
+ padding: 0 0.75rem;
1482
+ text-align: center;
1483
+ font-size: 11px;
1484
+ line-height: 1.35;
1485
+ color: hsl(var(--muted-foreground));
1486
+ }
1487
+ .composer-mermaid-msg--error {
1488
+ font-size: 10px;
1489
+ color: hsl(var(--destructive));
1490
+ }
1491
+ .composer-mermaid-code {
1492
+ border-radius: 0.25rem;
1493
+ background: hsl(var(--muted));
1494
+ padding-inline: 0.25rem;
1495
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1496
+ }
1497
+
1498
+ /* ----------------------------------------------------------------------------
1499
+ * Hint bar + keyboard chips.
1500
+ * ------------------------------------------------------------------------- */
1501
+ .composer-hint {
1502
+ text-align: center;
1503
+ font-size: 11px;
1504
+ color: hsl(var(--muted-foreground));
1505
+ }
1506
+ .composer-kbd {
1507
+ border-radius: 0.25rem;
1508
+ border: 1px solid hsl(var(--border));
1509
+ background: hsl(var(--card));
1510
+ padding: 0.0625rem 0.25rem;
1511
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1512
+ font-size: 10px;
1513
+ }
1514
+ .composer-hint-sm,
1515
+ .composer-hint-md {
1516
+ display: none;
1517
+ }
1518
+ @media (min-width: 640px) {
1519
+ .composer-hint-sm {
1520
+ display: inline;
1521
+ }
1522
+ }
1523
+ @media (min-width: 768px) {
1524
+ .composer-hint-md {
1525
+ display: inline;
1526
+ }
1527
+ }
1528
+
1529
+ /* ----------------------------------------------------------------------------
1530
+ * Quick-prompt + suggestion chip rows.
1531
+ * ------------------------------------------------------------------------- */
1532
+ .composer-prompts {
1533
+ display: flex;
1534
+ flex-wrap: wrap;
1535
+ align-items: center;
1536
+ gap: 0.5rem;
1537
+ padding: 0 0.25rem 0.25rem;
1538
+ }
1539
+ .composer-suggestions {
1540
+ display: flex;
1541
+ flex-wrap: wrap;
1542
+ justify-content: center;
1543
+ gap: 0.5rem;
1544
+ }
1545
+ .composer-prompt,
1546
+ .composer-suggestion {
1547
+ display: inline-flex;
1548
+ max-width: 100%;
1549
+ align-items: center;
1550
+ gap: 0.375rem;
1551
+ border-radius: 9999px;
1552
+ border: 1px solid hsl(var(--border));
1553
+ background: hsl(var(--card) / 0.6);
1554
+ padding: 0.375rem 0.875rem;
1555
+ font-size: 0.75rem;
1556
+ color: hsl(var(--muted-foreground));
1557
+ backdrop-filter: blur(4px);
1558
+ cursor: pointer;
1559
+ transition: transform 0.15s ease, border-color 0.15s ease,
1560
+ background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
1561
+ }
1562
+ .composer-prompt:hover,
1563
+ .composer-suggestion:hover {
1564
+ transform: translateY(-1px);
1565
+ border-color: hsl(var(--primary) / 0.4);
1566
+ background: hsl(var(--card));
1567
+ color: hsl(var(--foreground));
1568
+ box-shadow: 0 1px 2px hsl(220 13% 10% / 0.1);
1569
+ }
1570
+ .composer-prompt:focus-visible,
1571
+ .composer-suggestion:focus-visible {
1572
+ outline: none;
1573
+ box-shadow: 0 0 0 2px hsl(var(--primary) / 0.4);
1574
+ }
1575
+ .composer-prompt svg,
1576
+ .composer-suggestion svg {
1577
+ height: 0.75rem;
1578
+ width: 0.75rem;
1579
+ flex: none;
1580
+ color: hsl(var(--primary));
1581
+ opacity: 0.7;
1582
+ transition: opacity 0.15s ease;
1583
+ }
1584
+ .composer-prompt:hover svg,
1585
+ .composer-suggestion:hover svg {
1586
+ opacity: 1;
1587
+ }
1588
+ .composer-prompt-text {
1589
+ overflow: hidden;
1590
+ text-overflow: ellipsis;
1591
+ white-space: nowrap;
1592
+ }
1593
+
1594
+ /* ----------------------------------------------------------------------------
1595
+ * Tooltip.
1596
+ * ------------------------------------------------------------------------- */
1597
+ .composer-tooltip-wrap {
1598
+ position: relative;
1599
+ display: inline-flex;
1600
+ }
1601
+ .composer-tooltip {
1602
+ position: absolute;
1603
+ z-index: 50;
1604
+ white-space: nowrap;
1605
+ border-radius: 0.375rem;
1606
+ background: hsl(var(--foreground));
1607
+ padding: 0.25rem 0.5rem;
1608
+ font-size: 0.75rem;
1609
+ color: hsl(var(--background));
1610
+ box-shadow: var(--composer-shadow-soft);
1611
+ }
1612
+ .composer-tooltip--top {
1613
+ bottom: 100%;
1614
+ left: 50%;
1615
+ margin-bottom: 0.5rem;
1616
+ transform: translateX(-50%);
1617
+ }
1618
+ .composer-tooltip--bottom {
1619
+ top: 100%;
1620
+ left: 50%;
1621
+ margin-top: 0.5rem;
1622
+ transform: translateX(-50%);
1623
+ }
1624
+ .composer-tooltip--left {
1625
+ inset-inline-end: 100%;
1626
+ top: 50%;
1627
+ margin-inline-end: 0.5rem;
1628
+ transform: translateY(-50%);
1629
+ }
1630
+ .composer-tooltip--right {
1631
+ inset-inline-start: 100%;
1632
+ top: 50%;
1633
+ margin-inline-start: 0.5rem;
1634
+ transform: translateY(-50%);
481
1635
  }