myoperator-ui 0.0.189 → 0.0.190-beta.1

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 (2) hide show
  1. package/dist/index.js +322 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8391,17 +8391,26 @@ export const BankDetails = React.forwardRef<HTMLDivElement, BankDetailsProps>(
8391
8391
  icon,
8392
8392
  items,
8393
8393
  defaultOpen = true,
8394
+ open,
8395
+ onOpenChange,
8394
8396
  onCopy,
8395
8397
  className,
8396
8398
  },
8397
8399
  ref
8398
8400
  ) => {
8401
+ const isControlled = open !== undefined;
8402
+
8399
8403
  return (
8400
8404
  <div ref={ref} className={cn("w-full", className)}>
8401
8405
  <Accordion
8402
8406
  type="single"
8403
8407
  variant="bordered"
8404
- defaultValue={defaultOpen ? ["bank-details"] : []}
8408
+ {...(isControlled
8409
+ ? {
8410
+ value: open ? ["bank-details"] : [],
8411
+ onValueChange: (val) => onOpenChange?.(val.length > 0),
8412
+ }
8413
+ : { defaultValue: defaultOpen ? ["bank-details"] : [] })}
8405
8414
  >
8406
8415
  <AccordionItem value="bank-details">
8407
8416
  <AccordionTrigger className="px-4 py-4">
@@ -8535,6 +8544,10 @@ export interface BankDetailsProps {
8535
8544
  // Accordion
8536
8545
  /** Whether the accordion is open by default */
8537
8546
  defaultOpen?: boolean;
8547
+ /** Controlled open state \u2014 use with onOpenChange for exclusive accordion behavior */
8548
+ open?: boolean;
8549
+ /** Callback fired when the panel is toggled \u2014 receives new open state */
8550
+ onOpenChange?: (open: boolean) => void;
8538
8551
 
8539
8552
  // Callbacks
8540
8553
  /** Callback fired when a value is copied to clipboard */
@@ -11017,10 +11030,14 @@ export const WalletTopup = React.forwardRef<HTMLDivElement, WalletTopupProps>(
11017
11030
  loading = false,
11018
11031
  disabled = false,
11019
11032
  defaultOpen = true,
11033
+ open,
11034
+ onOpenChange,
11020
11035
  className,
11021
11036
  },
11022
11037
  ref
11023
11038
  ) => {
11039
+ const isOpenControlled = open !== undefined;
11040
+
11024
11041
  // Controlled/uncontrolled amount selection
11025
11042
  const isControlled = controlledAmount !== undefined;
11026
11043
  const [internalAmount, setInternalAmount] = React.useState<number | null>(
@@ -11189,7 +11206,12 @@ export const WalletTopup = React.forwardRef<HTMLDivElement, WalletTopupProps>(
11189
11206
  <Accordion
11190
11207
  type="single"
11191
11208
  variant="bordered"
11192
- defaultValue={defaultOpen ? ["wallet-topup"] : []}
11209
+ {...(isOpenControlled
11210
+ ? {
11211
+ value: open ? ["wallet-topup"] : [],
11212
+ onValueChange: (val) => onOpenChange?.(val.length > 0),
11213
+ }
11214
+ : { defaultValue: defaultOpen ? ["wallet-topup"] : [] })}
11193
11215
  >
11194
11216
  <AccordionItem value="wallet-topup">
11195
11217
  <AccordionTrigger className="px-4 py-4">
@@ -11537,6 +11559,10 @@ export interface WalletTopupProps {
11537
11559
  // Accordion
11538
11560
  /** Whether the accordion is open by default */
11539
11561
  defaultOpen?: boolean;
11562
+ /** Controlled open state \u2014 use with onOpenChange for exclusive accordion behavior */
11563
+ open?: boolean;
11564
+ /** Callback fired when the panel is toggled \u2014 receives new open state */
11565
+ onOpenChange?: (open: boolean) => void;
11540
11566
 
11541
11567
  // Styling
11542
11568
  /** Additional className for the root element */
@@ -11548,6 +11574,300 @@ export interface WalletTopupProps {
11548
11574
  name: "index.ts",
11549
11575
  content: prefixTailwindClasses(`export { WalletTopup } from "./wallet-topup";
11550
11576
  export type { WalletTopupProps, AmountOption } from "./types";
11577
+ `, prefix)
11578
+ }
11579
+ ]
11580
+ },
11581
+ "contact-form-modal": {
11582
+ name: "contact-form-modal",
11583
+ description: "A modal dialog for adding a new contact with phone number, country code, channel selector, and Start Conversation CTA",
11584
+ category: "custom",
11585
+ dependencies: [
11586
+ "clsx",
11587
+ "tailwind-merge",
11588
+ "lucide-react"
11589
+ ],
11590
+ internalDependencies: [
11591
+ "dialog",
11592
+ "select",
11593
+ "input",
11594
+ "button"
11595
+ ],
11596
+ isMultiFile: true,
11597
+ directory: "contact-form-modal",
11598
+ mainFile: "contact-form-modal.tsx",
11599
+ files: [
11600
+ {
11601
+ name: "contact-form-modal.tsx",
11602
+ content: prefixTailwindClasses(`import * as React from "react";
11603
+ import { cn } from "../../../lib/utils";
11604
+ import {
11605
+ Dialog,
11606
+ DialogContent,
11607
+ DialogHeader,
11608
+ DialogTitle,
11609
+ } from "../dialog";
11610
+ import {
11611
+ Select,
11612
+ SelectContent,
11613
+ SelectItem,
11614
+ SelectTrigger,
11615
+ SelectValue,
11616
+ } from "../select";
11617
+ import { Input } from "../input";
11618
+ import { Button } from "../button";
11619
+ import type { ContactFormModalProps, CountryCodeOption } from "./types";
11620
+
11621
+ /** Default country code options for the phone prefix selector */
11622
+ export const DEFAULT_COUNTRY_CODE_OPTIONS: CountryCodeOption[] = [
11623
+ { value: "+91", label: "+91", flag: "\u{1F1EE}\u{1F1F3}" },
11624
+ { value: "+1", label: "+1", flag: "\u{1F1FA}\u{1F1F8}" },
11625
+ { value: "+44", label: "+44", flag: "\u{1F1EC}\u{1F1E7}" },
11626
+ { value: "+61", label: "+61", flag: "\u{1F1E6}\u{1F1FA}" },
11627
+ { value: "+971", label: "+971", flag: "\u{1F1E6}\u{1F1EA}" },
11628
+ { value: "+65", label: "+65", flag: "\u{1F1F8}\u{1F1EC}" },
11629
+ ];
11630
+
11631
+ /**
11632
+ * ContactFormModal renders a dialog for adding a new contact. It provides
11633
+ * fields for phone number (with country-code prefix), contact name, an
11634
+ * optional channel selector, and a Start Conversation CTA.
11635
+ *
11636
+ * Install via CLI:
11637
+ * \`\`\`bash
11638
+ * npx myoperator-ui add contact-form-modal
11639
+ * \`\`\`
11640
+ *
11641
+ * @example
11642
+ * \`\`\`tsx
11643
+ * <ContactFormModal
11644
+ * open={open}
11645
+ * onOpenChange={setOpen}
11646
+ * onStartConversation={() => console.log("start")}
11647
+ * />
11648
+ * \`\`\`
11649
+ */
11650
+ export const ContactFormModal = React.forwardRef<
11651
+ HTMLDivElement,
11652
+ ContactFormModalProps
11653
+ >(
11654
+ (
11655
+ {
11656
+ open,
11657
+ onOpenChange,
11658
+ phoneNumber: phoneNumberProp,
11659
+ onPhoneNumberChange,
11660
+ contactName: contactNameProp,
11661
+ onContactNameChange,
11662
+ countryCodeOptions = DEFAULT_COUNTRY_CODE_OPTIONS,
11663
+ selectedCountryCode,
11664
+ onCountryCodeChange,
11665
+ channelOptions = [],
11666
+ selectedChannel,
11667
+ onChannelChange,
11668
+ startConversationDisabled,
11669
+ onStartConversation,
11670
+ className,
11671
+ },
11672
+ ref
11673
+ ) => {
11674
+ const [internalPhone, setInternalPhone] = React.useState("");
11675
+ const [internalName, setInternalName] = React.useState("");
11676
+ const [internalCountryCode, setInternalCountryCode] = React.useState(
11677
+ countryCodeOptions[0]?.value ?? "+91"
11678
+ );
11679
+
11680
+ const phoneNumber =
11681
+ phoneNumberProp !== undefined ? phoneNumberProp : internalPhone;
11682
+ const contactName =
11683
+ contactNameProp !== undefined ? contactNameProp : internalName;
11684
+ const countryCode =
11685
+ selectedCountryCode !== undefined
11686
+ ? selectedCountryCode
11687
+ : internalCountryCode;
11688
+
11689
+ const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
11690
+ const val = e.target.value;
11691
+ if (phoneNumberProp === undefined) setInternalPhone(val);
11692
+ onPhoneNumberChange?.(val);
11693
+ };
11694
+
11695
+ const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
11696
+ const val = e.target.value;
11697
+ if (contactNameProp === undefined) setInternalName(val);
11698
+ onContactNameChange?.(val);
11699
+ };
11700
+
11701
+ const handleCountryCodeChange = (val: string) => {
11702
+ if (selectedCountryCode === undefined) setInternalCountryCode(val);
11703
+ onCountryCodeChange?.(val);
11704
+ };
11705
+
11706
+ const isStartDisabled =
11707
+ startConversationDisabled !== undefined
11708
+ ? startConversationDisabled
11709
+ : phoneNumber.trim() === "";
11710
+
11711
+ return (
11712
+ <Dialog open={open} onOpenChange={onOpenChange}>
11713
+ <DialogContent
11714
+ ref={ref}
11715
+ className={cn("sm:max-w-[480px]", className)}
11716
+ >
11717
+ <DialogHeader>
11718
+ <DialogTitle>Add New Contact</DialogTitle>
11719
+ </DialogHeader>
11720
+
11721
+ <div className="flex flex-col gap-4 py-2">
11722
+ {/* Phone number */}
11723
+ <div className="flex flex-col gap-1.5">
11724
+ <label className="text-sm font-medium text-semantic-text-primary">
11725
+ Phone <span>*</span>
11726
+ </label>
11727
+ <div className="flex gap-2">
11728
+ {countryCodeOptions.length > 0 && (
11729
+ <Select
11730
+ value={countryCode}
11731
+ onValueChange={handleCountryCodeChange}
11732
+ >
11733
+ <SelectTrigger className="w-24 shrink-0">
11734
+ <SelectValue />
11735
+ </SelectTrigger>
11736
+ <SelectContent>
11737
+ {countryCodeOptions.map((opt) => (
11738
+ <SelectItem key={opt.value} value={opt.value}>
11739
+ {opt.flag ? \`\${opt.flag} \${opt.label}\` : opt.label}
11740
+ </SelectItem>
11741
+ ))}
11742
+ </SelectContent>
11743
+ </Select>
11744
+ )}
11745
+ <Input
11746
+ type="tel"
11747
+ placeholder="Enter phone number"
11748
+ value={phoneNumber}
11749
+ onChange={handlePhoneChange}
11750
+ className="flex-1"
11751
+ />
11752
+ </div>
11753
+ </div>
11754
+
11755
+ {/* Contact name */}
11756
+ <div className="flex flex-col gap-1.5">
11757
+ <label className="text-sm font-medium text-semantic-text-primary">
11758
+ Save contact as
11759
+ </label>
11760
+ <Input
11761
+ type="text"
11762
+ placeholder="Enter name"
11763
+ value={contactName}
11764
+ onChange={handleNameChange}
11765
+ />
11766
+ </div>
11767
+
11768
+ {/* Channel selector (optional) */}
11769
+ {channelOptions.length > 0 && (
11770
+ <div className="flex flex-col gap-1.5">
11771
+ <label className="text-sm font-medium text-semantic-text-primary">
11772
+ Channel
11773
+ </label>
11774
+ <Select
11775
+ value={selectedChannel}
11776
+ onValueChange={onChannelChange}
11777
+ >
11778
+ <SelectTrigger>
11779
+ <SelectValue placeholder="Select channel" />
11780
+ </SelectTrigger>
11781
+ <SelectContent>
11782
+ {channelOptions.map((opt) => (
11783
+ <SelectItem key={opt.value} value={opt.value}>
11784
+ {opt.label}
11785
+ </SelectItem>
11786
+ ))}
11787
+ </SelectContent>
11788
+ </Select>
11789
+ </div>
11790
+ )}
11791
+ </div>
11792
+
11793
+ {/* CTA */}
11794
+ <div className="flex justify-end pt-2">
11795
+ <Button
11796
+ onClick={onStartConversation}
11797
+ disabled={isStartDisabled}
11798
+ >
11799
+ Start Conversation
11800
+ </Button>
11801
+ </div>
11802
+ </DialogContent>
11803
+ </Dialog>
11804
+ );
11805
+ }
11806
+ );
11807
+
11808
+ ContactFormModal.displayName = "ContactFormModal";
11809
+ `, prefix)
11810
+ },
11811
+ {
11812
+ name: "types.ts",
11813
+ content: prefixTailwindClasses(`/** An option in the channel selector */
11814
+ export interface ChannelOption {
11815
+ value: string;
11816
+ label: string;
11817
+ }
11818
+
11819
+ /** A country code prefix option for the phone number field */
11820
+ export interface CountryCodeOption {
11821
+ value: string;
11822
+ label: string;
11823
+ flag?: string;
11824
+ }
11825
+
11826
+ /** Props for the ContactFormModal component */
11827
+ export interface ContactFormModalProps {
11828
+ /** Whether the modal is open */
11829
+ open: boolean;
11830
+ /** Callback when modal open state changes */
11831
+ onOpenChange: (open: boolean) => void;
11832
+
11833
+ /** Controlled phone number value */
11834
+ phoneNumber?: string;
11835
+ /** Callback when phone number changes */
11836
+ onPhoneNumberChange?: (value: string) => void;
11837
+
11838
+ /** Controlled contact name value */
11839
+ contactName?: string;
11840
+ /** Callback when contact name changes */
11841
+ onContactNameChange?: (value: string) => void;
11842
+
11843
+ /** Country code options for the phone prefix selector */
11844
+ countryCodeOptions?: CountryCodeOption[];
11845
+ /** Currently selected country code value */
11846
+ selectedCountryCode?: string;
11847
+ /** Callback when country code changes */
11848
+ onCountryCodeChange?: (value: string) => void;
11849
+
11850
+ /** Channel options for the channel selector (hidden when empty) */
11851
+ channelOptions?: ChannelOption[];
11852
+ /** Currently selected channel value */
11853
+ selectedChannel?: string;
11854
+ /** Callback when channel changes */
11855
+ onChannelChange?: (value: string) => void;
11856
+
11857
+ /** Override to explicitly disable the Start Conversation button */
11858
+ startConversationDisabled?: boolean;
11859
+ /** Callback when Start Conversation is clicked */
11860
+ onStartConversation?: () => void;
11861
+
11862
+ /** Additional className for the dialog content */
11863
+ className?: string;
11864
+ }
11865
+ `, prefix)
11866
+ },
11867
+ {
11868
+ name: "index.ts",
11869
+ content: prefixTailwindClasses(`export { ContactFormModal, DEFAULT_COUNTRY_CODE_OPTIONS } from "./contact-form-modal";
11870
+ export type { ContactFormModalProps, ChannelOption, CountryCodeOption } from "./types";
11551
11871
  `, prefix)
11552
11872
  }
11553
11873
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myoperator-ui",
3
- "version": "0.0.189",
3
+ "version": "0.0.190-beta.1",
4
4
  "description": "CLI for adding myOperator UI components to your project",
5
5
  "type": "module",
6
6
  "exports": "./dist/index.js",