@checkstack/ui 1.1.4 → 1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @checkstack/ui
2
2
 
3
+ ## 1.1.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 95aa716: Fix LDAP CA certificate input: The custom CA certificate field was rendered as a single-line password input, which stripped newlines from PEM certificates and caused TLS connection failures ("Failed to connect"). The field now renders as a multi-line secret textarea that properly preserves PEM format while still encrypting the value in storage.
8
+
3
9
  ## 1.1.4
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/ui",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "dependencies": {
@@ -140,6 +140,20 @@ export const FormField: React.FC<FormFieldProps> = ({
140
140
  propSchema as JsonSchemaProperty & { "x-secret"?: boolean }
141
141
  )["x-secret"];
142
142
 
143
+ // Secret textarea fields (e.g., PEM certificates)
144
+ if (isTextarea && isSecret) {
145
+ return (
146
+ <SecretTextareaField
147
+ id={id}
148
+ label={label}
149
+ description={cleanDesc}
150
+ value={value as string}
151
+ isRequired={isRequired}
152
+ onChange={onChange}
153
+ />
154
+ );
155
+ }
156
+
143
157
  // Textarea fields
144
158
  if (isTextarea) {
145
159
  return (
@@ -577,6 +591,56 @@ export const FormField: React.FC<FormFieldProps> = ({
577
591
  return <></>;
578
592
  };
579
593
 
594
+ /**
595
+ * Shared visibility toggle button for secret fields.
596
+ */
597
+ const VisibilityToggle: React.FC<{
598
+ visible: boolean;
599
+ onToggle: () => void;
600
+ }> = ({ visible, onToggle }) => (
601
+ <button
602
+ type="button"
603
+ onClick={onToggle}
604
+ className="absolute right-2 top-3 text-muted-foreground hover:text-foreground"
605
+ >
606
+ {visible ? (
607
+ <svg
608
+ className="h-5 w-5"
609
+ fill="none"
610
+ stroke="currentColor"
611
+ viewBox="0 0 24 24"
612
+ >
613
+ <path
614
+ strokeLinecap="round"
615
+ strokeLinejoin="round"
616
+ strokeWidth={2}
617
+ d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
618
+ />
619
+ </svg>
620
+ ) : (
621
+ <svg
622
+ className="h-5 w-5"
623
+ fill="none"
624
+ stroke="currentColor"
625
+ viewBox="0 0 24 24"
626
+ >
627
+ <path
628
+ strokeLinecap="round"
629
+ strokeLinejoin="round"
630
+ strokeWidth={2}
631
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
632
+ />
633
+ <path
634
+ strokeLinecap="round"
635
+ strokeLinejoin="round"
636
+ strokeWidth={2}
637
+ d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
638
+ />
639
+ </svg>
640
+ )}
641
+ </button>
642
+ );
643
+
580
644
  /**
581
645
  * Secret field component with password visibility toggle.
582
646
  * Extracted to keep hooks at component top level.
@@ -612,47 +676,91 @@ const SecretField: React.FC<{
612
676
  placeholder={hasExistingValue ? "••••••••" : "Enter secret value"}
613
677
  className="pr-10"
614
678
  />
615
- <button
616
- type="button"
617
- onClick={() => setShowPassword(!showPassword)}
618
- className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
619
- >
620
- {showPassword ? (
621
- <svg
622
- className="h-5 w-5"
623
- fill="none"
624
- stroke="currentColor"
625
- viewBox="0 0 24 24"
626
- >
627
- <path
628
- strokeLinecap="round"
629
- strokeLinejoin="round"
630
- strokeWidth={2}
631
- d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
632
- />
633
- </svg>
634
- ) : (
635
- <svg
636
- className="h-5 w-5"
637
- fill="none"
638
- stroke="currentColor"
639
- viewBox="0 0 24 24"
640
- >
641
- <path
642
- strokeLinecap="round"
643
- strokeLinejoin="round"
644
- strokeWidth={2}
645
- d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
646
- />
647
- <path
648
- strokeLinecap="round"
649
- strokeLinejoin="round"
650
- strokeWidth={2}
651
- d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
652
- />
653
- </svg>
654
- )}
655
- </button>
679
+ <VisibilityToggle
680
+ visible={showPassword}
681
+ onToggle={() => setShowPassword(!showPassword)}
682
+ />
683
+ </div>
684
+ {hasExistingValue && currentValue === "" && (
685
+ <p className="text-xs text-muted-foreground">
686
+ Leave empty to keep existing value
687
+ </p>
688
+ )}
689
+ </div>
690
+ );
691
+ };
692
+
693
+ /**
694
+ * Secret textarea field for multi-line secrets (e.g., PEM certificates).
695
+ * Renders a textarea with a visibility toggle and secret-like behavior.
696
+ */
697
+ const SecretTextareaField: React.FC<{
698
+ id: string;
699
+ label: string;
700
+ description?: string;
701
+ value: string;
702
+ isRequired?: boolean;
703
+ onChange: (val: unknown) => void;
704
+ }> = ({ id, label, description, value, isRequired, onChange }) => {
705
+ const [showContent, setShowContent] = React.useState(false);
706
+ const currentValue = value || "";
707
+ const hasExistingValue = currentValue.length > 0;
708
+
709
+ return (
710
+ <div className="space-y-2">
711
+ <div>
712
+ <Label htmlFor={id}>
713
+ {label} {isRequired && "*"}
714
+ </Label>
715
+ {description && (
716
+ <p className="text-sm text-muted-foreground mt-0.5">{description}</p>
717
+ )}
718
+ </div>
719
+ <div className="relative">
720
+ {showContent ? (
721
+ <Textarea
722
+ id={id}
723
+ value={currentValue}
724
+ onChange={(e) => onChange(e.target.value)}
725
+ placeholder={
726
+ hasExistingValue ? "Leave empty to keep existing value" : "Paste content here"
727
+ }
728
+ rows={5}
729
+ className="pr-10 font-mono text-xs"
730
+ />
731
+ ) : (
732
+ <Textarea
733
+ id={id}
734
+ value={currentValue ? "••••••••••••••••••••" : ""}
735
+ onChange={(e) => {
736
+ // If user types/pastes into masked field, switch to visible mode
737
+ const newVal = e.target.value.replaceAll("•", "");
738
+ if (newVal) {
739
+ setShowContent(true);
740
+ onChange(newVal);
741
+ }
742
+ }}
743
+ onPaste={(e) => {
744
+ // On paste, switch to visible mode and use pasted content
745
+ e.preventDefault();
746
+ const pastedText = e.clipboardData.getData("text");
747
+ if (pastedText) {
748
+ setShowContent(true);
749
+ onChange(pastedText);
750
+ }
751
+ }}
752
+ placeholder={
753
+ hasExistingValue ? "Leave empty to keep existing value" : "Paste content here"
754
+ }
755
+ rows={3}
756
+ className="pr-10"
757
+ readOnly={!!currentValue}
758
+ />
759
+ )}
760
+ <VisibilityToggle
761
+ visible={showContent}
762
+ onToggle={() => setShowContent(!showContent)}
763
+ />
656
764
  </div>
657
765
  {hasExistingValue && currentValue === "" && (
658
766
  <p className="text-xs text-muted-foreground">