@chai-ui/react 0.7.1 → 0.9.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.
@@ -14,7 +14,7 @@ import {
14
14
  } from "@chai-ui/core";
15
15
  import type { ModelOption, MediaKind, MediaAnalysisPromptLength } from "@chai-ui/core";
16
16
  import { SearchMenu } from "./primitives/SearchMenu.js";
17
- import { StopIcon } from "./icons.js";
17
+ import { ArrowForwardIcon, StopIcon } from "./icons.js";
18
18
  import type { MediaAnalyzerAttachment, MediaAnalyzerSubmitPayload } from "@chai-ui/core";
19
19
 
20
20
  export {
@@ -335,14 +335,6 @@ function MediaAnalyzerThumbnail({
335
335
  // Same "trace and inline as a currentColor component" convention Composer.tsx
336
336
  // documents — icons aren't shared across files yet in this codebase.
337
337
 
338
- function ArrowForwardIcon() {
339
- return (
340
- <svg viewBox="0 -960 960 960" aria-hidden="true">
341
- <path d="M686-450H160v-60h526L438-758l42-42 320 320-320 320-42-42 248-248Z" fill="currentColor" />
342
- </svg>
343
- );
344
- }
345
-
346
338
  /**
347
339
  * A stroked X, not the filled-path Material Symbols "close" glyph the rest
348
340
  * of this codebase traces (reference: Composer.tsx's own `CloseIcon`) —
@@ -12,7 +12,7 @@ import {
12
12
  type Result,
13
13
  } from "@chai-ui/core";
14
14
  import { Pagination } from "./primitives/Pagination.js";
15
- import { CheckSymbolIcon, DownloadIcon, ExpandIcon, ShareIcon } from "./icons.js";
15
+ import { CheckIcon, CheckSymbolIcon, DownloadIcon, ExpandIcon, ShareIcon } from "./icons.js";
16
16
  import { PageSlide, usePageSlide } from "./page-slide.js";
17
17
 
18
18
  /**
@@ -887,18 +887,6 @@ function CopyIcon() {
887
887
  );
888
888
  }
889
889
 
890
- /** MD3 "check" symbol — same traced glyph SearchMenu.tsx uses, marking a passed evaluation. */
891
- function CheckIcon() {
892
- return (
893
- <svg viewBox="0 0 18 18" fill="none" aria-hidden="true">
894
- <path
895
- d="M7.1625 13.5L2.8875 9.225L3.95625 8.15625L7.1625 11.3625L14.0438 4.48125L15.1125 5.55L7.1625 13.5Z"
896
- fill="currentColor"
897
- />
898
- </svg>
899
- );
900
- }
901
-
902
890
  /** Real Material Symbols "play_arrow" (viewBox `0 -960 960 960`) — not in the mock itself, see this section's own header comment. */
903
891
  function PlayIcon() {
904
892
  return (
package/src/icons.tsx CHANGED
@@ -127,3 +127,24 @@ export function StopIcon() {
127
127
  </svg>
128
128
  );
129
129
  }
130
+
131
+ /** Material Symbols "arrow_forward": Composer's submit and Media Analyzer's. */
132
+ export function ArrowForwardIcon() {
133
+ return (
134
+ <svg viewBox="0 -960 960 960" aria-hidden="true">
135
+ <path d="M686-450H160v-60h526L438-758l42-42 320 320-320 320-42-42 248-248Z" fill="currentColor" />
136
+ </svg>
137
+ );
138
+ }
139
+
140
+ /** MD3 "check" (traced from Figma): a selected option row, a passed evaluation. Not `CheckSymbolIcon`, the Material Symbols path. */
141
+ export function CheckIcon() {
142
+ return (
143
+ <svg viewBox="0 0 18 18" fill="none" aria-hidden="true">
144
+ <path
145
+ d="M7.1625 13.5L2.8875 9.225L3.95625 8.15625L7.1625 11.3625L14.0438 4.48125L15.1125 5.55L7.1625 13.5Z"
146
+ fill="currentColor"
147
+ />
148
+ </svg>
149
+ );
150
+ }
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ export * from "./useMediaAnalyzer.js";
11
11
  // to import from @chai-ui/react.
12
12
  export {
13
13
  createFalEngine,
14
+ createFalTranscriber,
14
15
  createOpenRouterEngine,
15
16
  createMockEngine,
16
17
  mockEngine,
@@ -40,6 +41,10 @@ export type {
40
41
  MediaAnalysisPromptLength,
41
42
  MediaKind,
42
43
  ModelOption,
44
+ OutputKind,
45
+ Transcribe,
46
+ VoiceClip,
47
+ VoiceInputConfig,
43
48
  ParameterOption,
44
49
  RegionBox,
45
50
  RegionFormat,
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } fr
4
4
  import { MdOutlinedTextField } from "@material/web/textfield/outlined-text-field.js";
5
5
  import { MdCheckbox } from "@material/web/checkbox/checkbox.js";
6
6
  import { Toggle } from "./Toggle.js";
7
+ import { CheckIcon } from "../icons.js";
7
8
 
8
9
  const MdOutlinedTextFieldElement = createComponent({
9
10
  react: React,
@@ -26,18 +27,6 @@ const MdCheckboxElement = createComponent({
26
27
  elementClass: MdCheckbox,
27
28
  });
28
29
 
29
- /** MD3 "check" symbol (Figma) — marking the selected row in any option list below. */
30
- function CheckIcon() {
31
- return (
32
- <svg viewBox="0 0 18 18" fill="none" aria-hidden="true">
33
- <path
34
- d="M7.1625 13.5L2.8875 9.225L3.95625 8.15625L7.1625 11.3625L14.0438 4.48125L15.1125 5.55L7.1625 13.5Z"
35
- fill="currentColor"
36
- />
37
- </svg>
38
- );
39
- }
40
-
41
30
  export interface SearchMenuOption {
42
31
  id: string;
43
32
  /** Shown on the trigger once this option is selected, and used for search-filtering. Keep this the short/canonical name. */
package/src/style.css CHANGED
@@ -593,6 +593,75 @@
593
593
  opacity: 0.6;
594
594
  }
595
595
 
596
+ /* Create world's two prompts, side by side (stacked when narrow): each a
597
+ label and its own Enhance button above a filled box. */
598
+ .chai-composer__world-fields {
599
+ display: flex;
600
+ flex-wrap: wrap;
601
+ gap: var(--chai-space-4);
602
+ }
603
+
604
+ .chai-composer__world-field {
605
+ flex: 1 1 240px;
606
+ min-width: 0;
607
+ display: flex;
608
+ flex-direction: column;
609
+ gap: var(--chai-space-2);
610
+ }
611
+
612
+ .chai-composer__world-field-head {
613
+ display: flex;
614
+ align-items: center;
615
+ justify-content: space-between;
616
+ min-height: 24px;
617
+ }
618
+
619
+ .chai-composer__world-field-label {
620
+ font-size: var(--chai-type-size-xs);
621
+ font-weight: var(--chai-type-weight-material-medium);
622
+ line-height: var(--chai-type-leading-label-small);
623
+ letter-spacing: var(--chai-type-tracking-label-small);
624
+ color: var(--chai-color-semantic-control-text-strong);
625
+ }
626
+
627
+ .chai-composer__world-field-label strong {
628
+ font-weight: var(--chai-type-weight-semibold);
629
+ }
630
+
631
+ /* In the heading row, not over the text: no absolute position. */
632
+ .chai-composer__world-field-head .chai-composer__optimize {
633
+ position: static;
634
+ width: 24px;
635
+ }
636
+
637
+ .chai-composer__field--boxed {
638
+ min-height: 144px;
639
+ padding: var(--chai-space-3);
640
+ border-radius: var(--chai-radius-md);
641
+ background: var(--chai-color-semantic-control-field);
642
+ }
643
+
644
+ /* Smaller text than the bar's prompt, and no room kept for an Enhance button. */
645
+ .chai-composer__field--boxed .chai-composer__type {
646
+ padding-right: 0;
647
+ font-size: var(--chai-type-size-body-medium);
648
+ line-height: var(--chai-type-leading-body-medium);
649
+ letter-spacing: var(--chai-type-tracking-body-medium);
650
+ }
651
+
652
+ /* Why submit can't go yet (a world with no environment). */
653
+ .chai-composer__field-hint {
654
+ margin: 0;
655
+ font-size: var(--chai-type-size-xs);
656
+ line-height: var(--chai-type-leading-label-small);
657
+ letter-spacing: var(--chai-type-tracking-label-small);
658
+ color: var(--chai-composer-placeholder);
659
+ }
660
+
661
+ .chai-composer__field--boxed .chai-composer__reveal {
662
+ inset: var(--chai-space-3);
663
+ }
664
+
596
665
  /* The optimize/undo reveal (Composer.tsx's own doc comment on the overlay
597
666
  for the full mechanics) — the real textarea's text goes fully
598
667
  transparent so only the overlay's progressive, gradient-lit copy shows
@@ -682,6 +751,99 @@
682
751
  flex-wrap: wrap;
683
752
  }
684
753
 
754
+ .chai-composer__controls-end {
755
+ display: flex;
756
+ align-items: center;
757
+ gap: var(--chai-space-2);
758
+ }
759
+
760
+ /* Voice input: the mic left of submit. While recording it becomes a stop
761
+ button (a filled square in a ringed circle) and a waveform replaces the
762
+ prompt; while transcribing, a spinner. */
763
+ .chai-composer__voice {
764
+ display: flex;
765
+ align-items: center;
766
+ }
767
+
768
+ .chai-composer__voice-button {
769
+ display: flex;
770
+ align-items: center;
771
+ justify-content: center;
772
+ width: 40px;
773
+ height: 40px;
774
+ padding: 0;
775
+ border: 2px solid transparent;
776
+ border-radius: var(--chai-radius-full);
777
+ background: transparent;
778
+ color: var(--chai-composer-text);
779
+ cursor: pointer;
780
+ transition: background-color var(--chai-motion-duration-fast) var(--chai-motion-easing-standard);
781
+ }
782
+
783
+ .chai-composer__voice-button:hover:not(:disabled) {
784
+ background: var(--chai-composer-hover);
785
+ }
786
+
787
+ .chai-composer__voice-button:disabled {
788
+ cursor: not-allowed;
789
+ opacity: 0.5;
790
+ }
791
+
792
+ .chai-composer__voice-button svg {
793
+ width: 24px;
794
+ height: 24px;
795
+ }
796
+
797
+ .chai-composer__voice[data-status="recording"] .chai-composer__voice-button {
798
+ border-color: var(--chai-composer-text);
799
+ background: var(--chai-composer-hover);
800
+ }
801
+
802
+ .chai-composer__voice[data-status="recording"] .chai-composer__voice-button svg {
803
+ width: 18px;
804
+ height: 18px;
805
+ }
806
+
807
+ /* Same row height as the prompt it stands in for. */
808
+ .chai-composer__waveform {
809
+ display: flex;
810
+ align-items: center;
811
+ gap: var(--chai-space-3);
812
+ min-height: var(--chai-type-leading-body-large);
813
+ }
814
+
815
+ .chai-composer__waveform-bars {
816
+ flex: 1;
817
+ display: flex;
818
+ align-items: center;
819
+ justify-content: space-between;
820
+ height: var(--chai-type-leading-body-large);
821
+ min-width: 0;
822
+ }
823
+
824
+ .chai-composer__waveform-bar {
825
+ width: 2px;
826
+ border-radius: var(--chai-radius-full);
827
+ background: var(--chai-composer-text);
828
+ }
829
+
830
+ .chai-composer__waveform-time {
831
+ color: var(--chai-composer-text);
832
+ font-family: var(--chai-type-family-material);
833
+ font-size: var(--chai-type-size-label-large);
834
+ font-weight: var(--chai-type-weight-material-medium);
835
+ font-variant-numeric: tabular-nums;
836
+ }
837
+
838
+ .chai-composer__voice-spinner {
839
+ width: 20px;
840
+ height: 20px;
841
+ border-radius: 50%;
842
+ border: 2px solid var(--chai-color-semantic-control-border-faint);
843
+ border-top-color: var(--chai-color-semantic-accent);
844
+ animation: chai-spin var(--chai-motion-duration-steep) linear infinite;
845
+ }
846
+
685
847
  /* Neutral trigger text, not `--md-sys-color-primary` green (account
686
848
  holder correction: "don't make dropdown select menu buttons green when
687
849
  they don't need to be"). */
@@ -2339,7 +2501,9 @@
2339
2501
  draw `<md-focus-ring>`. The Composer's textarea is the one control that
2340
2502
  had none — its ring goes on the whole pill, matching MD3's ring width
2341
2503
  and color so every ring in a component reads as the same system. */
2342
- .chai-composer:has(.chai-composer__input:focus-visible) {
2504
+ .chai-composer:has(.chai-composer__field:not(.chai-composer__field--boxed) .chai-composer__input:focus-visible),
2505
+ /* A world's two boxes: the ring marks the box being typed in, not the bar. */
2506
+ .chai-composer__field--boxed:has(.chai-composer__input:focus-visible) {
2343
2507
  outline: 3px solid var(--chai-color-semantic-accent);
2344
2508
  outline-offset: 2px;
2345
2509
  }
@@ -2365,6 +2529,7 @@
2365
2529
  }
2366
2530
 
2367
2531
  [data-chai-focus-ring="hidden"] .chai-composer:has(.chai-composer__input:focus-visible),
2532
+ [data-chai-focus-ring="hidden"] .chai-composer__field--boxed:has(.chai-composer__input:focus-visible),
2368
2533
  [data-chai-focus-ring="hidden"] :is(
2369
2534
  .chai-composer,
2370
2535
  .chai-media-analyzer,