@intlayer/design-system 8.7.6 → 8.7.7
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/dist/esm/components/Breadcrumb/index.mjs.map +1 -1
- package/dist/esm/components/Browser/Browser.mjs.map +1 -1
- package/dist/esm/components/Carousel/index.mjs.map +1 -1
- package/dist/esm/components/DictionaryEditor/NodeWrapper/BooleanWrapper.mjs.map +1 -1
- package/dist/esm/components/DictionaryEditor/NodeWrapper/NestedObjectWrapper.mjs.map +1 -1
- package/dist/esm/components/DictionaryEditor/NodeWrapper/NumberWrapper.mjs.map +1 -1
- package/dist/esm/components/DictionaryEditor/NodeWrapper/index.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/EnumKeyInput.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/KeyPathBreadcrumb.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NodeTypeSelector.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs.map +1 -1
- package/dist/esm/components/EditableField/EditableFieldInput.mjs.map +1 -1
- package/dist/esm/components/Flags/Flag.mjs.map +1 -1
- package/dist/esm/components/Form/FormField.mjs.map +1 -1
- package/dist/esm/components/Form/FormItem.mjs.map +1 -1
- package/dist/esm/components/Form/elements/FormElementWrapper.mjs.map +1 -1
- package/dist/esm/components/HTMLRender/HTMLRender.mjs.map +1 -1
- package/dist/esm/components/Headers/index.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockServer.mjs.map +1 -1
- package/dist/esm/components/IDE/FileTree.mjs.map +1 -1
- package/dist/esm/components/IDE/IDE.mjs.map +1 -1
- package/dist/esm/components/Input/OTPInput.mjs.map +1 -1
- package/dist/esm/components/KeyboardShortcut/KeyboardShortcut.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherDropDown/LocaleSwitcher.mjs.map +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/Navbar/index.mjs +1 -3
- package/dist/esm/components/Navbar/index.mjs.map +1 -1
- package/dist/esm/components/Navbar/useNavigation.mjs +3 -0
- package/dist/esm/components/Navbar/useNavigation.mjs.map +1 -1
- package/dist/esm/components/Popover/dynamic.mjs.map +1 -1
- package/dist/esm/components/Select/Multiselect.mjs.map +1 -1
- package/dist/esm/components/Tab/Tab.mjs.map +1 -1
- package/dist/esm/components/TechLogo/TechLogo.mjs.map +1 -1
- package/dist/esm/components/TextArea/AutocompleteTextArea.mjs.map +1 -1
- package/dist/esm/components/TextArea/ContentEditableTextArea.mjs.map +1 -1
- package/dist/esm/components/Toaster/Toast.mjs +6 -1
- package/dist/esm/components/Toaster/Toast.mjs.map +1 -1
- package/dist/esm/components/WithResizer/index.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +2 -2
- package/dist/esm/hooks/reactQuery.mjs +47 -2
- package/dist/esm/hooks/reactQuery.mjs.map +1 -1
- package/dist/esm/hooks/useAuth/useOAuth2.mjs +1 -1
- package/dist/esm/hooks/useAuth/useOAuth2.mjs.map +1 -1
- package/dist/esm/hooks/useAuth/useSession.mjs +1 -1
- package/dist/esm/hooks/useAuth/useSession.mjs.map +1 -1
- package/dist/esm/hooks/useDevice.mjs.map +1 -1
- package/dist/esm/hooks/useHorizontalSwipe.mjs.map +1 -1
- package/dist/esm/hooks/useIsDarkMode.mjs.map +1 -1
- package/dist/esm/hooks/useKeyboardDetector.mjs.map +1 -1
- package/dist/esm/hooks/useScrollBlockage/useScrollBlockageStore.mjs.map +1 -1
- package/dist/esm/libs/auth.mjs +1 -1
- package/dist/esm/libs/auth.mjs.map +1 -1
- package/dist/esm/providers/ReactQueryProvider.mjs.map +1 -1
- package/dist/esm/tailwind.config.mjs.map +1 -1
- package/dist/types/components/Badge/index.d.ts +2 -2
- package/dist/types/components/Button/Button.d.ts +5 -5
- package/dist/types/components/Command/index.d.ts +2 -2
- package/dist/types/components/Container/index.d.ts +7 -7
- package/dist/types/components/Input/Checkbox.d.ts +3 -3
- package/dist/types/components/Input/Input.d.ts +1 -1
- package/dist/types/components/Link/Link.d.ts +2 -2
- package/dist/types/components/Pagination/Pagination.d.ts +1 -1
- package/dist/types/components/SwitchSelector/index.d.ts +1 -1
- package/dist/types/components/TabSelector/TabSelector.d.ts +1 -1
- package/dist/types/components/Tag/index.d.ts +2 -2
- package/dist/types/components/Toaster/Toast.d.ts +1 -1
- package/dist/types/hooks/index.d.ts +2 -2
- package/dist/types/hooks/reactQuery.d.ts +8 -1
- package/dist/types/hooks/reactQuery.d.ts.map +1 -1
- package/package.json +25 -25
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Multiselect.mjs","names":["RemoveIcon"],"sources":["../../../../src/components/Select/Multiselect.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { Check, X as RemoveIcon } from 'lucide-react';\nimport {\n type ComponentProps,\n createContext,\n type Dispatch,\n type FC,\n type HTMLAttributes,\n type KeyboardEvent,\n type LegacyRef,\n type MouseEventHandler,\n type RefObject,\n type SetStateAction,\n type SyntheticEvent,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { Badge, BadgeColor } from '../Badge';\nimport { Command, CommandRoot } from '../Command';\n\n/**\n * Context properties for MultiSelect component state management\n *\n * @interface MultiSelectContextProps\n */\ntype MultiSelectContextProps = {\n /** Array of currently selected values */\n value: string[];\n /** Handler for value changes */\n onValueChange: (value: string) => void;\n /** Whether the dropdown is currently open */\n open: boolean;\n /** Function to set the open state */\n setOpen: (value: boolean) => void;\n /** Current input field value for filtering */\n inputValue: string;\n /** Function to set the input value */\n setInputValue: Dispatch<SetStateAction<string>>;\n /** Index of currently focused option for keyboard navigation */\n activeIndex: number;\n /** Function to set the active index */\n setActiveIndex: Dispatch<SetStateAction<number>>;\n /** Ref to the input element */\n ref: RefObject<HTMLInputElement | null>;\n /** Handler for option selection */\n handleSelect: (e: SyntheticEvent<HTMLInputElement>) => void;\n};\n\nconst MultiSelectContext = createContext<MultiSelectContextProps | null>(null);\n\n/**\n * Custom hook to access MultiSelect context\n *\n * Provides access to the internal state and methods of the MultiSelect component.\n * Must be used within a MultiSelect component tree.\n *\n * @returns MultiSelectContextProps - All context properties and methods\n * @throws Error when used outside of MultiSelect component\n *\n * @example\n * ```tsx\n * function CustomMultiSelectItem() {\n * const { value, onValueChange, open } = useMultiSelect();\n * // Use context properties...\n * }\n * ```\n */\nconst useMultiSelect = () => {\n const context = useContext(MultiSelectContext);\n if (!context) {\n throw new Error('useMultiSelect must be used within MultiSelectProvider');\n }\n return context;\n};\n\n/**\n * Props interface for the main MultiSelect component\n *\n * @interface MultiSelectProps\n */\ntype MultiSelectProps = ComponentProps<typeof CommandRoot> & {\n /**\n * Array of selected values (controlled mode)\n * @example\n * ```tsx\n * const [selected, setSelected] = useState(['react', 'vue']);\n * <MultiSelect values={selected} onValueChange={setSelected} />\n * ```\n */\n values?: string[];\n\n /**\n * Default selected values for uncontrolled mode\n * @example\n * ```tsx\n * <MultiSelect defaultValues={['react']} />\n * ```\n */\n defaultValues?: string[];\n\n /**\n * Callback fired when selection changes\n * @param value - New array of selected values\n * @example\n * ```tsx\n * <MultiSelect onValueChange={(values) => console.log('Selected:', values)} />\n * ```\n */\n onValueChange?: (value: string[]) => void;\n\n /**\n * Whether keyboard navigation should loop through options\n * @default false\n * @example\n * ```tsx\n * <MultiSelect loop /> // Arrow keys wrap around at list boundaries\n * ```\n */\n loop?: boolean;\n};\n\n/**\n * MultiSelect - A comprehensive multi-selection dropdown component\n *\n * An advanced multi-select component that combines the functionality of a searchable dropdown\n * with the ability to select multiple values. Built on top of Command component primitives,\n * it provides filtering, keyboard navigation, and visual feedback through badges.\n *\n * ## Key Features\n * - **Multi-Selection**: Select multiple options with visual badge representation\n * - **Searchable**: Built-in filtering to quickly find options in large lists\n * - **Keyboard Navigation**: Full arrow key navigation with optional looping\n * - **Accessibility**: Screen reader support, ARIA attributes, and focus management\n * - **Flexible State**: Both controlled and uncontrolled usage patterns\n * - **Rich UI**: Customizable badges, icons, and content layout\n *\n * ## Use Cases\n * - Tag/category selection in forms\n * - Multi-user assignment interfaces\n * - Feature/permission selection\n * - Filter selection in search interfaces\n * - Any multi-choice selection requirement\n *\n * ## Architecture\n * The component follows a compound pattern similar to Select:\n * - `MultiSelect` (root): Manages state and provides context\n * - `MultiSelect.Trigger`: Container for input and selected badges\n * - `MultiSelect.Input`: Searchable input field with filtering\n * - `MultiSelect.Content`: Dropdown container for options\n * - `MultiSelect.List`: Options container with keyboard navigation\n * - `MultiSelect.Item`: Individual selectable options\n *\n * ## Accessibility\n * - **Keyboard Navigation**: Arrow keys, Enter to select, Backspace to remove\n * - **Screen Readers**: Proper ARIA labels and live region announcements\n * - **Focus Management**: Clear focus indicators and logical tab flow\n * - **Search**: Real-time filtering with screen reader announcements\n *\n * @example\n * Basic multi-select usage:\n * ```tsx\n * const [frameworks, setFrameworks] = useState<string[]>([]);\n *\n * <MultiSelect values={frameworks} onValueChange={setFrameworks}>\n * <MultiSelect.Trigger>\n * <MultiSelect.Input placeholder=\"Select frameworks...\" />\n * </MultiSelect.Trigger>\n * <MultiSelect.Content>\n * <MultiSelect.List>\n * <MultiSelect.Item value=\"react\">React</MultiSelect.Item>\n * <MultiSelect.Item value=\"vue\">Vue</MultiSelect.Item>\n * <MultiSelect.Item value=\"svelte\">Svelte</MultiSelect.Item>\n * </MultiSelect.List>\n * </MultiSelect.Content>\n * </MultiSelect>\n * ```\n *\n * @example\n * Advanced usage with keyboard looping:\n * ```tsx\n * <MultiSelect defaultValues={['react']} loop>\n * <MultiSelect.Trigger>\n * <MultiSelect.Input placeholder=\"Choose technologies...\" />\n * </MultiSelect.Trigger>\n * <MultiSelect.Content>\n * <MultiSelect.List>\n * <MultiSelect.Item value=\"react\">⚛️ React</MultiSelect.Item>\n * <MultiSelect.Item value=\"vue\">💚 Vue</MultiSelect.Item>\n * <MultiSelect.Item value=\"angular\">🔴 Angular</MultiSelect.Item>\n * </MultiSelect.List>\n * </MultiSelect.Content>\n * </MultiSelect>\n * ```\n *\n * @example\n * Form integration with validation:\n * ```tsx\n * <form>\n * <MultiSelect\n * values={selectedSkills}\n * onValueChange={setSelectedSkills}\n * required\n * >\n * <MultiSelect.Trigger className=\"min-h-[2.5rem]\">\n * <MultiSelect.Input placeholder=\"Select your skills...\" />\n * </MultiSelect.Trigger>\n * <MultiSelect.Content>\n * <MultiSelect.List>\n * <MultiSelect.Item value=\"javascript\">JavaScript</MultiSelect.Item>\n * <MultiSelect.Item value=\"typescript\">TypeScript</MultiSelect.Item>\n * <MultiSelect.Item value=\"python\">Python</MultiSelect.Item>\n * </MultiSelect.List>\n * </MultiSelect.Content>\n * </MultiSelect>\n * </form>\n * ```\n */\nconst MultiSelectRoot: FC<MultiSelectProps> = ({\n values: valuesProp,\n defaultValues,\n onValueChange,\n loop = false,\n className,\n children,\n dir,\n ...props\n}) => {\n const [value, setValue] = useState<string[]>(defaultValues ?? []);\n const [inputValue, setInputValue] = useState('');\n const [open, setOpen] = useState<boolean>(false);\n const [activeIndex, setActiveIndex] = useState<number>(-1);\n const inputRef = useRef<HTMLInputElement>(null);\n const [isValueSelected, setIsValueSelected] = useState(false);\n const [selectedValue, setSelectedValue] = useState('');\n\n useEffect(() => {\n if (valuesProp) {\n setValue(valuesProp);\n }\n }, [valuesProp]);\n\n const onValueChangeHandler = useCallback(\n (val: string) => {\n if (value.includes(val)) {\n const newValue = value.filter((item) => item !== val);\n setValue(newValue);\n onValueChange?.(newValue);\n } else {\n const newValue = [...value, val];\n setValue(newValue);\n onValueChange?.(newValue);\n }\n },\n\n [value]\n );\n\n const handleSelect = useCallback(\n (e: SyntheticEvent<HTMLInputElement>) => {\n e.preventDefault();\n const target = e.currentTarget;\n const selection = target.value.substring(\n target.selectionStart ?? 0,\n target.selectionEnd ?? 0\n );\n\n setSelectedValue(selection);\n setIsValueSelected(selection === inputValue);\n },\n [inputValue]\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n e.stopPropagation();\n const target = inputRef.current;\n\n if (!target) return;\n\n const moveNext = () => {\n const nextIndex = activeIndex + 1;\n setActiveIndex(\n nextIndex > value.length - 1 ? (loop ? 0 : -1) : nextIndex\n );\n };\n\n const movePrev = () => {\n const prevIndex = activeIndex - 1;\n setActiveIndex(prevIndex < 0 ? value.length - 1 : prevIndex);\n };\n\n const moveCurrent = () => {\n const newIndex =\n activeIndex - 1 <= 0\n ? value.length - 1 === 0\n ? -1\n : 0\n : activeIndex - 1;\n setActiveIndex(newIndex);\n };\n\n switch (e.key) {\n case 'ArrowLeft':\n if (dir === 'rtl') {\n if (value.length > 0 && (activeIndex !== -1 || loop)) {\n moveNext();\n }\n } else if (value.length > 0 && target.selectionStart === 0) {\n movePrev();\n }\n break;\n\n case 'ArrowRight':\n if (dir === 'rtl') {\n if (value.length > 0 && target.selectionStart === 0) {\n movePrev();\n }\n } else if (value.length > 0 && (activeIndex !== -1 || loop)) {\n moveNext();\n }\n break;\n\n case 'Backspace':\n case 'Delete':\n if (value.length > 0) {\n if (activeIndex !== -1 && activeIndex < value.length) {\n onValueChangeHandler(value[activeIndex]);\n moveCurrent();\n } else if (\n (target.selectionStart === 0 && selectedValue === inputValue) ||\n isValueSelected\n ) {\n onValueChangeHandler(value[value.length - 1]);\n }\n }\n break;\n\n case 'Enter':\n setOpen(true);\n break;\n\n case 'Escape':\n if (activeIndex !== -1) {\n setActiveIndex(-1);\n } else if (open) {\n setOpen(false);\n }\n break;\n }\n },\n\n [value, inputValue, activeIndex, loop]\n );\n\n const memoValue = useMemo(\n () => ({\n value,\n onValueChange: onValueChangeHandler,\n open,\n setOpen,\n inputValue,\n setInputValue,\n activeIndex,\n setActiveIndex,\n ref: inputRef,\n handleSelect,\n }),\n [\n value,\n onValueChangeHandler,\n open,\n setOpen,\n inputValue,\n setInputValue,\n activeIndex,\n setActiveIndex,\n inputRef,\n handleSelect,\n ]\n );\n\n return (\n <MultiSelectContext value={memoValue}>\n <CommandRoot\n onKeyDown={handleKeyDown}\n className={cn(\n 'flex w-full flex-col gap-2 overflow-visible bg-transparent',\n className\n )}\n dir={dir}\n {...props}\n >\n {children}\n </CommandRoot>\n </MultiSelectContext>\n );\n};\n\nconst MultiSelectTrigger: FC<\n HTMLAttributes<HTMLDivElement> & {\n getBadgeValue?: (value: string) => string;\n validationStyleEnabled?: boolean;\n }\n> = ({\n className,\n getBadgeValue = (value) => value,\n validationStyleEnabled = false,\n children,\n ...props\n}) => {\n const { value, onValueChange, activeIndex } = useMultiSelect();\n\n const mousePreventDefault: MouseEventHandler<HTMLButtonElement> = useCallback(\n (e) => {\n e.preventDefault();\n e.stopPropagation();\n },\n []\n );\n\n return (\n <div\n className={cn(\n // Base layout\n 'flex w-full flex-col gap-3',\n 'cursor-pointer select-text text-base shadow-none outline-none md:text-sm',\n\n // Corner shape\n 'rounded-xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-2xl',\n\n // Spacing\n 'px-2 py-3 md:py-2',\n\n // Background and text\n 'bg-neutral-50 dark:bg-neutral-950',\n 'text-text',\n\n // Focus ring\n 'ring-0',\n 'focus-within:outline-none',\n 'focus-within:ring-3',\n 'focus-within:ring-neutral-200',\n 'dark:focus-within:ring-neutral-500',\n\n 'focus-within:ring-offset-white',\n 'dark:focus-within:ring-offset-neutral-500',\n\n // Remove box-shadow\n '[box-shadow:none]',\n\n // States\n 'disabled:cursor-not-allowed disabled:opacity-50',\n 'aria-invalid:border-error',\n\n // Validation styles\n validationStyleEnabled && 'valid:border-success invalid:border-error',\n\n className\n )}\n {...props}\n >\n {value.length > 0 && (\n <div className=\"flex w-full flex-wrap gap-1\">\n {value.map((item, index) => (\n <Badge\n key={item}\n className={cn(\n 'flex items-center gap-1 rounded-xl px-1',\n activeIndex === index && 'ring-2 ring-muted-foreground'\n )}\n color={BadgeColor.TEXT}\n >\n <span className=\"text-xs\">{getBadgeValue(item)}</span>\n <button\n aria-label={`Remove ${item} option`}\n aria-roledescription=\"button to remove option\"\n onMouseDown={mousePreventDefault}\n onClick={() => onValueChange(item)}\n >\n <span className=\"sr-only\">Remove {item} option</span>\n <RemoveIcon className=\"size-4 cursor-pointer\" />\n </button>\n </Badge>\n ))}\n </div>\n )}\n {children}\n </div>\n );\n};\n\nconst MultiSelectInput: FC<ComponentProps<typeof Command.Input>> = ({\n className,\n ...props\n}) => {\n const {\n setOpen,\n inputValue,\n setInputValue,\n activeIndex,\n setActiveIndex,\n handleSelect,\n ref: inputRef,\n } = useMultiSelect();\n\n return (\n <Command.Input\n {...props}\n tabIndex={0}\n ref={inputRef as LegacyRef<HTMLInputElement>}\n value={inputValue}\n onValueChange={activeIndex === -1 ? setInputValue : undefined}\n onSelect={handleSelect}\n onBlur={() => setOpen(false)}\n onFocus={() => setOpen(true)}\n onClick={() => setActiveIndex(-1)}\n className={cn(\n 'ml-2 flex-1 cursor-pointer outline-hidden',\n className,\n activeIndex !== -1 && 'caret-transparent'\n )}\n />\n );\n};\n\nconst MultiSelectContent: FC<HTMLAttributes<HTMLDivElement>> = ({\n children,\n}) => {\n const { open } = useMultiSelect();\n return <div className=\"relative\">{open && children}</div>;\n};\n\nconst MultiSelectList: typeof Command.List = ({ className, children }) => (\n <Command.List\n className={cn(\n // Base layout\n 'absolute top-0 z-10 flex w-full flex-col gap-2',\n 'rounded-xl p-2 shadow-md',\n\n // Background and text\n 'bg-white dark:bg-neutral-950',\n 'text-text',\n\n // Border\n 'border border-neutral-200 dark:border-neutral-800',\n\n // Transitions\n 'transition-colors',\n\n className\n )}\n >\n {children}\n <Command.Empty>\n <span className=\"text-muted-foreground\">No results found</span>\n </Command.Empty>\n </Command.List>\n);\n\nconst MultiSelectItem: FC<\n { value: string } & ComponentProps<typeof Command.Item>\n> = ({ className, value, children, ...props }) => {\n const { value: Options, onValueChange, setInputValue } = useMultiSelect();\n\n const mousePreventDefault: MouseEventHandler<HTMLDivElement> = useCallback(\n (e) => {\n e.preventDefault();\n e.stopPropagation();\n },\n []\n );\n\n const isIncluded = Options.includes(value);\n return (\n <Command.Item\n {...props}\n onSelect={() => {\n onValueChange(value);\n setInputValue('');\n }}\n className={cn(\n // Base layout\n 'flex cursor-pointer justify-between',\n 'rounded-lg px-2 py-1',\n\n // Hover and transitions\n 'transition-colors',\n 'hover:bg-neutral/10',\n\n // States\n isIncluded && 'opacity-50',\n props.disabled && 'cursor-not-allowed opacity-50',\n\n className\n )}\n onMouseDown={mousePreventDefault}\n >\n {children}\n {isIncluded && <Check className=\"size-4\" />}\n </Command.Item>\n );\n};\n\ntype MultiSelectType = typeof MultiSelectRoot & {\n Trigger: typeof MultiSelectTrigger;\n Input: typeof MultiSelectInput;\n Content: typeof MultiSelectContent;\n List: typeof MultiSelectList;\n Item: typeof MultiSelectItem;\n};\n\n/**\n *\n * Usage example:\n * ```jsx\n * <MultiSelect\n * values={value}\n * onValuesChange={setValue}\n * loop\n * >\n * <MultiSelect.Trigger>\n * <MultiSelect.Input placeholder=\"Select your framework\" />\n * </MultiSelect.Trigger>\n * <MultiSelect.Content>\n * <MultiSelect.List>\n * <MultiSelect.Item value={\"React\"}>React</MultiSelect.Item>\n * <MultiSelect.Item value={\"Vue\"}>Vue</MultiSelect.Item>\n * <MultiSelect.Item value={\"Svelte\"}>Svelte</MultiSelect.Item>\n * </MultiSelect.List>\n * </MultiSelect.Content>\n * </MultiSelect>\n * ```\n */\nexport const MultiSelect = MultiSelectRoot as MultiSelectType;\nMultiSelect.Trigger = MultiSelectTrigger;\nMultiSelect.Input = MultiSelectInput;\nMultiSelect.Content = MultiSelectContent;\nMultiSelect.List = MultiSelectList;\nMultiSelect.Item = MultiSelectItem;\n"],"mappings":";;;;;;;;;;AAsDA,MAAM,qBAAqB,cAA8C,KAAK;;;;;;;;;;;;;;;;;;AAmB9E,MAAM,uBAAuB;CAC3B,MAAM,UAAU,WAAW,mBAAmB;AAC9C,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,yDAAyD;AAE3E,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiJT,MAAM,mBAAyC,EAC7C,QAAQ,YACR,eACA,eACA,OAAO,OACP,WACA,UACA,KACA,GAAG,YACC;CACJ,MAAM,CAAC,OAAO,YAAY,SAAmB,iBAAiB,EAAE,CAAC;CACjE,MAAM,CAAC,YAAY,iBAAiB,SAAS,GAAG;CAChD,MAAM,CAAC,MAAM,WAAW,SAAkB,MAAM;CAChD,MAAM,CAAC,aAAa,kBAAkB,SAAiB,GAAG;CAC1D,MAAM,WAAW,OAAyB,KAAK;CAC/C,MAAM,CAAC,iBAAiB,sBAAsB,SAAS,MAAM;CAC7D,MAAM,CAAC,eAAe,oBAAoB,SAAS,GAAG;AAEtD,iBAAgB;AACd,MAAI,WACF,UAAS,WAAW;IAErB,CAAC,WAAW,CAAC;CAEhB,MAAM,uBAAuB,aAC1B,QAAgB;AACf,MAAI,MAAM,SAAS,IAAI,EAAE;GACvB,MAAM,WAAW,MAAM,QAAQ,SAAS,SAAS,IAAI;AACrD,YAAS,SAAS;AAClB,mBAAgB,SAAS;SACpB;GACL,MAAM,WAAW,CAAC,GAAG,OAAO,IAAI;AAChC,YAAS,SAAS;AAClB,mBAAgB,SAAS;;IAI7B,CAAC,MAAM,CACR;CAED,MAAM,eAAe,aAClB,MAAwC;AACvC,IAAE,gBAAgB;EAClB,MAAM,SAAS,EAAE;EACjB,MAAM,YAAY,OAAO,MAAM,UAC7B,OAAO,kBAAkB,GACzB,OAAO,gBAAgB,EACxB;AAED,mBAAiB,UAAU;AAC3B,qBAAmB,cAAc,WAAW;IAE9C,CAAC,WAAW,CACb;CAED,MAAM,gBAAgB,aACnB,MAAqC;AACpC,IAAE,iBAAiB;EACnB,MAAM,SAAS,SAAS;AAExB,MAAI,CAAC,OAAQ;EAEb,MAAM,iBAAiB;GACrB,MAAM,YAAY,cAAc;AAChC,kBACE,YAAY,MAAM,SAAS,IAAK,OAAO,IAAI,KAAM,UAClD;;EAGH,MAAM,iBAAiB;GACrB,MAAM,YAAY,cAAc;AAChC,kBAAe,YAAY,IAAI,MAAM,SAAS,IAAI,UAAU;;EAG9D,MAAM,oBAAoB;AAOxB,kBALE,cAAc,KAAK,IACf,MAAM,SAAS,MAAM,IACnB,KACA,IACF,cAAc,EACI;;AAG1B,UAAQ,EAAE,KAAV;GACE,KAAK;AACH,QAAI,QAAQ,OACV;SAAI,MAAM,SAAS,MAAM,gBAAgB,MAAM,MAC7C,WAAU;eAEH,MAAM,SAAS,KAAK,OAAO,mBAAmB,EACvD,WAAU;AAEZ;GAEF,KAAK;AACH,QAAI,QAAQ,OACV;SAAI,MAAM,SAAS,KAAK,OAAO,mBAAmB,EAChD,WAAU;eAEH,MAAM,SAAS,MAAM,gBAAgB,MAAM,MACpD,WAAU;AAEZ;GAEF,KAAK;GACL,KAAK;AACH,QAAI,MAAM,SAAS,GACjB;SAAI,gBAAgB,MAAM,cAAc,MAAM,QAAQ;AACpD,2BAAqB,MAAM,aAAa;AACxC,mBAAa;gBAEZ,OAAO,mBAAmB,KAAK,kBAAkB,cAClD,gBAEA,sBAAqB,MAAM,MAAM,SAAS,GAAG;;AAGjD;GAEF,KAAK;AACH,YAAQ,KAAK;AACb;GAEF,KAAK;AACH,QAAI,gBAAgB,GAClB,gBAAe,GAAG;aACT,KACT,SAAQ,MAAM;AAEhB;;IAIN;EAAC;EAAO;EAAY;EAAa;EAAK,CACvC;AA6BD,QACE,oBAAC,oBAAD;EAAoB,OA5BJ,eACT;GACL;GACA,eAAe;GACf;GACA;GACA;GACA;GACA;GACA;GACA,KAAK;GACL;GACD,GACD;GACE;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CACF;YAIG,oBAAC,aAAD;GACE,WAAW;GACX,WAAW,GACT,8DACA,UACD;GACI;GACL,GAAI;GAEH;GACW;EACK;;AAIzB,MAAM,sBAKD,EACH,WACA,iBAAiB,UAAU,OAC3B,yBAAyB,OACzB,UACA,GAAG,YACC;CACJ,MAAM,EAAE,OAAO,eAAe,gBAAgB,gBAAgB;CAE9D,MAAM,sBAA4D,aAC/D,MAAM;AACL,IAAE,gBAAgB;AAClB,IAAE,iBAAiB;IAErB,EAAE,CACH;AAED,QACE,qBAAC,OAAD;EACE,WAAW,GAET,8BACA,4EAGA,mFAGA,qBAGA,qCACA,aAGA,UACA,6BACA,uBACA,iCACA,sCAEA,kCACA,6CAGA,qBAGA,mDACA,6BAGA,0BAA0B,6CAE1B,UACD;EACD,GAAI;YAtCN,CAwCG,MAAM,SAAS,KACd,oBAAC,OAAD;GAAK,WAAU;aACZ,MAAM,KAAK,MAAM,UAChB,qBAAC,OAAD;IAEE,WAAW,GACT,2CACA,gBAAgB,SAAS,+BAC1B;IACD;cANF,CAQE,oBAAC,QAAD;KAAM,WAAU;eAAW,cAAc,KAAK;KAAQ,GACtD,qBAAC,UAAD;KACE,cAAY,UAAU,KAAK;KAC3B,wBAAqB;KACrB,aAAa;KACb,eAAe,cAAc,KAAK;eAJpC,CAME,qBAAC,QAAD;MAAM,WAAU;gBAAhB;OAA0B;OAAQ;OAAK;OAAc;SACrD,oBAACA,GAAD,EAAY,WAAU,yBAA0B,EACzC;OACH;MAjBD,KAiBC,CACR;GACE,GAEP,SACG;;;AAIV,MAAM,oBAA8D,EAClE,WACA,GAAG,YACC;CACJ,MAAM,EACJ,SACA,YACA,eACA,aACA,gBACA,cACA,KAAK,aACH,gBAAgB;AAEpB,QACE,oBAAC,QAAQ,OAAT;EACE,GAAI;EACJ,UAAU;EACV,KAAK;EACL,OAAO;EACP,eAAe,gBAAgB,KAAK,gBAAgB;EACpD,UAAU;EACV,cAAc,QAAQ,MAAM;EAC5B,eAAe,QAAQ,KAAK;EAC5B,eAAe,eAAe,GAAG;EACjC,WAAW,GACT,6CACA,WACA,gBAAgB,MAAM,oBACvB;EACD;;AAIN,MAAM,sBAA0D,EAC9D,eACI;CACJ,MAAM,EAAE,SAAS,gBAAgB;AACjC,QAAO,oBAAC,OAAD;EAAK,WAAU;YAAY,QAAQ;EAAe;;AAG3D,MAAM,mBAAwC,EAAE,WAAW,eACzD,qBAAC,QAAQ,MAAT;CACE,WAAW,GAET,kDACA,4BAGA,gCACA,aAGA,qDAGA,qBAEA,UACD;WAjBH,CAmBG,UACD,oBAAC,QAAQ,OAAT,YACE,oBAAC,QAAD;EAAM,WAAU;YAAwB;EAAuB,GACjD,EACH;;AAGjB,MAAM,mBAED,EAAE,WAAW,OAAO,UAAU,GAAG,YAAY;CAChD,MAAM,EAAE,OAAO,SAAS,eAAe,kBAAkB,gBAAgB;CAEzE,MAAM,sBAAyD,aAC5D,MAAM;AACL,IAAE,gBAAgB;AAClB,IAAE,iBAAiB;IAErB,EAAE,CACH;CAED,MAAM,aAAa,QAAQ,SAAS,MAAM;AAC1C,QACE,qBAAC,QAAQ,MAAT;EACE,GAAI;EACJ,gBAAgB;AACd,iBAAc,MAAM;AACpB,iBAAc,GAAG;;EAEnB,WAAW,GAET,uCACA,wBAGA,qBACA,uBAGA,cAAc,cACd,MAAM,YAAY,iCAElB,UACD;EACD,aAAa;YArBf,CAuBG,UACA,cAAc,oBAAC,OAAD,EAAO,WAAU,UAAW,EAC9B;;;;;;;;;;;;;;;;;;;;;;;;;AAkCnB,MAAa,cAAc;AAC3B,YAAY,UAAU;AACtB,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,YAAY,OAAO;AACnB,YAAY,OAAO"}
|
|
1
|
+
{"version":3,"file":"Multiselect.mjs","names":["RemoveIcon"],"sources":["../../../../src/components/Select/Multiselect.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { Check, X as RemoveIcon } from 'lucide-react';\nimport {\n type ComponentProps,\n createContext,\n type Dispatch,\n type FC,\n type HTMLAttributes,\n type KeyboardEvent,\n type LegacyRef,\n type MouseEventHandler,\n type RefObject,\n type SetStateAction,\n type SyntheticEvent,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { Badge, BadgeColor } from '../Badge';\nimport { Command, CommandRoot } from '../Command';\n\n/**\n * Context properties for MultiSelect component state management\n *\n * @interface MultiSelectContextProps\n */\ntype MultiSelectContextProps = {\n /** Array of currently selected values */\n value: string[];\n /** Handler for value changes */\n onValueChange: (value: string) => void;\n /** Whether the dropdown is currently open */\n open: boolean;\n /** Function to set the open state */\n setOpen: (value: boolean) => void;\n /** Current input field value for filtering */\n inputValue: string;\n /** Function to set the input value */\n setInputValue: Dispatch<SetStateAction<string>>;\n /** Index of currently focused option for keyboard navigation */\n activeIndex: number;\n /** Function to set the active index */\n setActiveIndex: Dispatch<SetStateAction<number>>;\n /** Ref to the input element */\n ref: RefObject<HTMLInputElement | null>;\n /** Handler for option selection */\n handleSelect: (e: SyntheticEvent<HTMLInputElement>) => void;\n};\n\nconst MultiSelectContext = createContext<MultiSelectContextProps | null>(null);\n\n/**\n * Custom hook to access MultiSelect context\n *\n * Provides access to the internal state and methods of the MultiSelect component.\n * Must be used within a MultiSelect component tree.\n *\n * @returns MultiSelectContextProps - All context properties and methods\n * @throws Error when used outside of MultiSelect component\n *\n * @example\n * ```tsx\n * function CustomMultiSelectItem() {\n * const { value, onValueChange, open } = useMultiSelect();\n * // Use context properties...\n * }\n * ```\n */\nconst useMultiSelect = () => {\n const context = useContext(MultiSelectContext);\n if (!context) {\n throw new Error('useMultiSelect must be used within MultiSelectProvider');\n }\n return context;\n};\n\n/**\n * Props interface for the main MultiSelect component\n *\n * @interface MultiSelectProps\n */\ntype MultiSelectProps = ComponentProps<typeof CommandRoot> & {\n /**\n * Array of selected values (controlled mode)\n * @example\n * ```tsx\n * const [selected, setSelected] = useState(['react', 'vue']);\n * <MultiSelect values={selected} onValueChange={setSelected} />\n * ```\n */\n values?: string[];\n\n /**\n * Default selected values for uncontrolled mode\n * @example\n * ```tsx\n * <MultiSelect defaultValues={['react']} />\n * ```\n */\n defaultValues?: string[];\n\n /**\n * Callback fired when selection changes\n * @param value - New array of selected values\n * @example\n * ```tsx\n * <MultiSelect onValueChange={(values) => console.log('Selected:', values)} />\n * ```\n */\n onValueChange?: (value: string[]) => void;\n\n /**\n * Whether keyboard navigation should loop through options\n * @default false\n * @example\n * ```tsx\n * <MultiSelect loop /> // Arrow keys wrap around at list boundaries\n * ```\n */\n loop?: boolean;\n};\n\n/**\n * MultiSelect - A comprehensive multi-selection dropdown component\n *\n * An advanced multi-select component that combines the functionality of a searchable dropdown\n * with the ability to select multiple values. Built on top of Command component primitives,\n * it provides filtering, keyboard navigation, and visual feedback through badges.\n *\n * ## Key Features\n * - **Multi-Selection**: Select multiple options with visual badge representation\n * - **Searchable**: Built-in filtering to quickly find options in large lists\n * - **Keyboard Navigation**: Full arrow key navigation with optional looping\n * - **Accessibility**: Screen reader support, ARIA attributes, and focus management\n * - **Flexible State**: Both controlled and uncontrolled usage patterns\n * - **Rich UI**: Customizable badges, icons, and content layout\n *\n * ## Use Cases\n * - Tag/category selection in forms\n * - Multi-user assignment interfaces\n * - Feature/permission selection\n * - Filter selection in search interfaces\n * - Any multi-choice selection requirement\n *\n * ## Architecture\n * The component follows a compound pattern similar to Select:\n * - `MultiSelect` (root): Manages state and provides context\n * - `MultiSelect.Trigger`: Container for input and selected badges\n * - `MultiSelect.Input`: Searchable input field with filtering\n * - `MultiSelect.Content`: Dropdown container for options\n * - `MultiSelect.List`: Options container with keyboard navigation\n * - `MultiSelect.Item`: Individual selectable options\n *\n * ## Accessibility\n * - **Keyboard Navigation**: Arrow keys, Enter to select, Backspace to remove\n * - **Screen Readers**: Proper ARIA labels and live region announcements\n * - **Focus Management**: Clear focus indicators and logical tab flow\n * - **Search**: Real-time filtering with screen reader announcements\n *\n * @example\n * Basic multi-select usage:\n * ```tsx\n * const [frameworks, setFrameworks] = useState<string[]>([]);\n *\n * <MultiSelect values={frameworks} onValueChange={setFrameworks}>\n * <MultiSelect.Trigger>\n * <MultiSelect.Input placeholder=\"Select frameworks...\" />\n * </MultiSelect.Trigger>\n * <MultiSelect.Content>\n * <MultiSelect.List>\n * <MultiSelect.Item value=\"react\">React</MultiSelect.Item>\n * <MultiSelect.Item value=\"vue\">Vue</MultiSelect.Item>\n * <MultiSelect.Item value=\"svelte\">Svelte</MultiSelect.Item>\n * </MultiSelect.List>\n * </MultiSelect.Content>\n * </MultiSelect>\n * ```\n *\n * @example\n * Advanced usage with keyboard looping:\n * ```tsx\n * <MultiSelect defaultValues={['react']} loop>\n * <MultiSelect.Trigger>\n * <MultiSelect.Input placeholder=\"Choose technologies...\" />\n * </MultiSelect.Trigger>\n * <MultiSelect.Content>\n * <MultiSelect.List>\n * <MultiSelect.Item value=\"react\">⚛️ React</MultiSelect.Item>\n * <MultiSelect.Item value=\"vue\">💚 Vue</MultiSelect.Item>\n * <MultiSelect.Item value=\"angular\">🔴 Angular</MultiSelect.Item>\n * </MultiSelect.List>\n * </MultiSelect.Content>\n * </MultiSelect>\n * ```\n *\n * @example\n * Form integration with validation:\n * ```tsx\n * <form>\n * <MultiSelect\n * values={selectedSkills}\n * onValueChange={setSelectedSkills}\n * required\n * >\n * <MultiSelect.Trigger className=\"min-h-[2.5rem]\">\n * <MultiSelect.Input placeholder=\"Select your skills...\" />\n * </MultiSelect.Trigger>\n * <MultiSelect.Content>\n * <MultiSelect.List>\n * <MultiSelect.Item value=\"javascript\">JavaScript</MultiSelect.Item>\n * <MultiSelect.Item value=\"typescript\">TypeScript</MultiSelect.Item>\n * <MultiSelect.Item value=\"python\">Python</MultiSelect.Item>\n * </MultiSelect.List>\n * </MultiSelect.Content>\n * </MultiSelect>\n * </form>\n * ```\n */\nconst MultiSelectRoot: FC<MultiSelectProps> = ({\n values: valuesProp,\n defaultValues,\n onValueChange,\n loop = false,\n className,\n children,\n dir,\n ...props\n}) => {\n const [value, setValue] = useState<string[]>(defaultValues ?? []);\n const [inputValue, setInputValue] = useState('');\n const [open, setOpen] = useState<boolean>(false);\n const [activeIndex, setActiveIndex] = useState<number>(-1);\n const inputRef = useRef<HTMLInputElement>(null);\n const [isValueSelected, setIsValueSelected] = useState(false);\n const [selectedValue, setSelectedValue] = useState('');\n\n useEffect(() => {\n if (valuesProp) {\n setValue(valuesProp);\n }\n }, [valuesProp]);\n\n const onValueChangeHandler = useCallback(\n (val: string) => {\n if (value.includes(val)) {\n const newValue = value.filter((item) => item !== val);\n setValue(newValue);\n onValueChange?.(newValue);\n } else {\n const newValue = [...value, val];\n setValue(newValue);\n onValueChange?.(newValue);\n }\n },\n\n [value]\n );\n\n const handleSelect = useCallback(\n (e: SyntheticEvent<HTMLInputElement>) => {\n e.preventDefault();\n const target = e.currentTarget;\n const selection = target.value.substring(\n target.selectionStart ?? 0,\n target.selectionEnd ?? 0\n );\n\n setSelectedValue(selection);\n setIsValueSelected(selection === inputValue);\n },\n [inputValue]\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n e.stopPropagation();\n const target = inputRef.current;\n\n if (!target) return;\n\n const moveNext = () => {\n const nextIndex = activeIndex + 1;\n setActiveIndex(\n nextIndex > value.length - 1 ? (loop ? 0 : -1) : nextIndex\n );\n };\n\n const movePrev = () => {\n const prevIndex = activeIndex - 1;\n setActiveIndex(prevIndex < 0 ? value.length - 1 : prevIndex);\n };\n\n const moveCurrent = () => {\n const newIndex =\n activeIndex - 1 <= 0\n ? value.length - 1 === 0\n ? -1\n : 0\n : activeIndex - 1;\n setActiveIndex(newIndex);\n };\n\n switch (e.key) {\n case 'ArrowLeft':\n if (dir === 'rtl') {\n if (value.length > 0 && (activeIndex !== -1 || loop)) {\n moveNext();\n }\n } else if (value.length > 0 && target.selectionStart === 0) {\n movePrev();\n }\n break;\n\n case 'ArrowRight':\n if (dir === 'rtl') {\n if (value.length > 0 && target.selectionStart === 0) {\n movePrev();\n }\n } else if (value.length > 0 && (activeIndex !== -1 || loop)) {\n moveNext();\n }\n break;\n\n case 'Backspace':\n case 'Delete':\n if (value.length > 0) {\n if (activeIndex !== -1 && activeIndex < value.length) {\n onValueChangeHandler(value[activeIndex]);\n moveCurrent();\n } else if (\n (target.selectionStart === 0 && selectedValue === inputValue) ||\n isValueSelected\n ) {\n onValueChangeHandler(value[value.length - 1]);\n }\n }\n break;\n\n case 'Enter':\n setOpen(true);\n break;\n\n case 'Escape':\n if (activeIndex !== -1) {\n setActiveIndex(-1);\n } else if (open) {\n setOpen(false);\n }\n break;\n }\n },\n\n [value, inputValue, activeIndex, loop]\n );\n\n const memoValue = useMemo(\n () => ({\n value,\n onValueChange: onValueChangeHandler,\n open,\n setOpen,\n inputValue,\n setInputValue,\n activeIndex,\n setActiveIndex,\n ref: inputRef,\n handleSelect,\n }),\n [\n value,\n onValueChangeHandler,\n open,\n setOpen,\n inputValue,\n setInputValue,\n activeIndex,\n setActiveIndex,\n inputRef,\n handleSelect,\n ]\n );\n\n return (\n <MultiSelectContext value={memoValue}>\n <CommandRoot\n onKeyDown={handleKeyDown}\n className={cn(\n 'flex w-full flex-col gap-2 overflow-visible bg-transparent',\n className\n )}\n dir={dir}\n {...props}\n >\n {children}\n </CommandRoot>\n </MultiSelectContext>\n );\n};\n\nconst MultiSelectTrigger: FC<\n HTMLAttributes<HTMLDivElement> & {\n getBadgeValue?: (value: string) => string;\n validationStyleEnabled?: boolean;\n }\n> = ({\n className,\n getBadgeValue = (value) => value,\n validationStyleEnabled = false,\n children,\n ...props\n}) => {\n const { value, onValueChange, activeIndex } = useMultiSelect();\n\n const mousePreventDefault: MouseEventHandler<HTMLButtonElement> = useCallback(\n (e) => {\n e.preventDefault();\n e.stopPropagation();\n },\n []\n );\n\n return (\n <div\n className={cn(\n // Base layout\n 'flex w-full flex-col gap-3',\n 'cursor-pointer select-text text-base shadow-none outline-none md:text-sm',\n\n // Corner shape\n 'rounded-xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-2xl',\n\n // Spacing\n 'px-2 py-3 md:py-2',\n\n // Background and text\n 'bg-neutral-50 dark:bg-neutral-950',\n 'text-text',\n\n // Focus ring\n 'ring-0',\n 'focus-within:outline-none',\n 'focus-within:ring-3',\n 'focus-within:ring-neutral-200',\n 'dark:focus-within:ring-neutral-500',\n\n 'focus-within:ring-offset-white',\n 'dark:focus-within:ring-offset-neutral-500',\n\n // Remove box-shadow\n '[box-shadow:none]',\n\n // States\n 'disabled:cursor-not-allowed disabled:opacity-50',\n 'aria-invalid:border-error',\n\n // Validation styles\n validationStyleEnabled && 'valid:border-success invalid:border-error',\n\n className\n )}\n {...props}\n >\n {value.length > 0 && (\n <div className=\"flex w-full flex-wrap gap-1\">\n {value.map((item, index) => (\n <Badge\n key={item}\n className={cn(\n 'flex items-center gap-1 rounded-xl px-1',\n activeIndex === index && 'ring-2 ring-muted-foreground'\n )}\n color={BadgeColor.TEXT}\n >\n <span className=\"text-xs\">{getBadgeValue(item)}</span>\n <button\n aria-label={`Remove ${item} option`}\n aria-roledescription=\"button to remove option\"\n onMouseDown={mousePreventDefault}\n onClick={() => onValueChange(item)}\n >\n <span className=\"sr-only\">Remove {item} option</span>\n <RemoveIcon className=\"size-4 cursor-pointer\" />\n </button>\n </Badge>\n ))}\n </div>\n )}\n {children}\n </div>\n );\n};\n\nconst MultiSelectInput: FC<ComponentProps<typeof Command.Input>> = ({\n className,\n ...props\n}) => {\n const {\n setOpen,\n inputValue,\n setInputValue,\n activeIndex,\n setActiveIndex,\n handleSelect,\n ref: inputRef,\n } = useMultiSelect();\n\n return (\n <Command.Input\n {...props}\n tabIndex={0}\n ref={inputRef as LegacyRef<HTMLInputElement>}\n value={inputValue}\n onValueChange={activeIndex === -1 ? setInputValue : undefined}\n onSelect={handleSelect}\n onBlur={() => setOpen(false)}\n onFocus={() => setOpen(true)}\n onClick={() => setActiveIndex(-1)}\n className={cn(\n 'ml-2 flex-1 cursor-pointer outline-hidden',\n className,\n activeIndex !== -1 && 'caret-transparent'\n )}\n />\n );\n};\n\nconst MultiSelectContent: FC<HTMLAttributes<HTMLDivElement>> = ({\n children,\n}) => {\n const { open } = useMultiSelect();\n return <div className=\"relative\">{open && children}</div>;\n};\n\nconst MultiSelectList: typeof Command.List = ({ className, children }) => (\n <Command.List\n className={cn(\n // Base layout\n 'absolute top-0 z-10 flex w-full flex-col gap-2',\n 'rounded-xl p-2 shadow-md',\n\n // Background and text\n 'bg-white dark:bg-neutral-950',\n 'text-text',\n\n // Border\n 'border border-neutral-200 dark:border-neutral-800',\n\n // Transitions\n 'transition-colors',\n\n className\n )}\n >\n {children}\n <Command.Empty>\n <span className=\"text-muted-foreground\">No results found</span>\n </Command.Empty>\n </Command.List>\n);\n\nconst MultiSelectItem: FC<\n { value: string } & ComponentProps<typeof Command.Item>\n> = ({ className, value, children, ...props }) => {\n const { value: Options, onValueChange, setInputValue } = useMultiSelect();\n\n const mousePreventDefault: MouseEventHandler<HTMLDivElement> = useCallback(\n (e) => {\n e.preventDefault();\n e.stopPropagation();\n },\n []\n );\n\n const isIncluded = Options.includes(value);\n return (\n <Command.Item\n {...props}\n onSelect={() => {\n onValueChange(value);\n setInputValue('');\n }}\n className={cn(\n // Base layout\n 'flex cursor-pointer justify-between',\n 'rounded-lg px-2 py-1',\n\n // Hover and transitions\n 'transition-colors',\n 'hover:bg-neutral/10',\n\n // States\n isIncluded && 'opacity-50',\n props.disabled && 'cursor-not-allowed opacity-50',\n\n className\n )}\n onMouseDown={mousePreventDefault}\n >\n {children}\n {isIncluded && <Check className=\"size-4\" />}\n </Command.Item>\n );\n};\n\ntype MultiSelectType = typeof MultiSelectRoot & {\n Trigger: typeof MultiSelectTrigger;\n Input: typeof MultiSelectInput;\n Content: typeof MultiSelectContent;\n List: typeof MultiSelectList;\n Item: typeof MultiSelectItem;\n};\n\n/**\n *\n * Usage example:\n * ```jsx\n * <MultiSelect\n * values={value}\n * onValuesChange={setValue}\n * loop\n * >\n * <MultiSelect.Trigger>\n * <MultiSelect.Input placeholder=\"Select your framework\" />\n * </MultiSelect.Trigger>\n * <MultiSelect.Content>\n * <MultiSelect.List>\n * <MultiSelect.Item value={\"React\"}>React</MultiSelect.Item>\n * <MultiSelect.Item value={\"Vue\"}>Vue</MultiSelect.Item>\n * <MultiSelect.Item value={\"Svelte\"}>Svelte</MultiSelect.Item>\n * </MultiSelect.List>\n * </MultiSelect.Content>\n * </MultiSelect>\n * ```\n */\nexport const MultiSelect = MultiSelectRoot as MultiSelectType;\nMultiSelect.Trigger = MultiSelectTrigger;\nMultiSelect.Input = MultiSelectInput;\nMultiSelect.Content = MultiSelectContent;\nMultiSelect.List = MultiSelectList;\nMultiSelect.Item = MultiSelectItem;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tab.mjs","names":[],"sources":["../../../../src/components/Tab/Tab.tsx"],"sourcesContent":["'use client';\n\nimport { useHorizontalSwipe } from '@hooks/useHorizontalSwipe';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n Children,\n createContext,\n type HTMLAttributes,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useState,\n} from 'react';\nimport { TabSelector, TabSelectorColor } from '../TabSelector';\nimport { useTabContext } from './TabContext';\n\n// Context for managing tab state\ntype TabContextType = {\n activeTab: string;\n setActiveTab: (tab: string) => void;\n};\n\nconst TabContext = createContext<TabContextType | undefined>(undefined);\n\n// Tab container variants\nconst tabContainerVariant = cva('relative w-full rounded-lg', {\n variants: {\n background: {\n with: 'border border-neutral/20 bg-background/2 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur',\n without: '',\n },\n variant: {\n default: '',\n bordered: 'border-2',\n ghost: 'border-0 bg-transparent shadow-none',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n});\n\nexport type TabProps = HTMLAttributes<HTMLDivElement> &\n VariantProps<typeof tabContainerVariant> & {\n defaultTab?: string;\n group?: string;\n children: ReactNode;\n headerClassName?: string;\n fullHeight?: boolean;\n };\n\nexport type TabItemProps = HTMLAttributes<HTMLDivElement> & {\n label: string;\n value: string;\n disabled?: boolean;\n children: ReactNode;\n};\n\n/**\n * TabItem component that represents a single tab\n * Must be used as a child of the Tab component\n */\nconst TabItem = ({ children, ...props }: TabItemProps) => (\n // This component is primarily used for its props by the parent Tab component\n // The actual rendering is handled by the Tab component\n <div {...props}>{children}</div>\n);\n\n// Add display name for better debugging\nTabItem.displayName = 'TabItem';\n\n/**\n * Tab container component that manages tab state and renders tab headers and content\n *\n * Example:\n * ```jsx\n * <Tab defaultTab=\"tab1\">\n * <Tab.Item label=\"First Tab\" value=\"tab1\">\n * Content for first tab\n * </Tab.Item>\n * <Tab.Item label=\"Second Tab\" value=\"tab2\">\n * Content for second tab\n * </Tab.Item>\n * </Tab>\n * ```\n */\nconst TabComponent = ({\n defaultTab,\n group,\n variant,\n children,\n className,\n headerClassName,\n fullHeight,\n ...props\n}: TabProps) => {\n // Extract TabItem children to get their props\n const tabItems = Children.toArray(children).filter((child) => {\n return isValidElement(child) && child.type === TabItem;\n }) as ReactElement<TabItemProps>[];\n\n const firstTabValue = tabItems[0]?.props?.value;\n const { tabsValues, setTabsValues } = useTabContext();\n const [activeTab, setActiveTab] = useState(defaultTab ?? firstTabValue ?? '');\n const hasGroup = group && typeof tabsValues === 'object';\n const currentTabValue =\n (hasGroup ? tabsValues?.[group] : activeTab) ?? defaultTab ?? firstTabValue;\n const activeTabIndex = tabItems.findIndex(\n (tab) => tab.props.value === currentTabValue\n );\n\n const tabsCount = tabItems.length;\n\n const { containerProps, dragDeltaPct, isDragging } = useHorizontalSwipe({\n itemIndex: activeTabIndex,\n itemCount: tabsCount,\n onSwipeLeft: () => {\n const targetIndex = Math.min(tabsCount - 1, activeTabIndex + 1);\n const nextValue = tabItems[targetIndex]?.props?.value;\n if (nextValue) handleSetActiveTab(nextValue);\n },\n onSwipeRight: () => {\n const targetIndex = Math.max(0, activeTabIndex - 1);\n const nextValue = tabItems[targetIndex]?.props?.value;\n if (nextValue) handleSetActiveTab(nextValue);\n },\n });\n\n const handleSetActiveTab = (tab: string) => {\n setActiveTab(tab);\n\n if (typeof setTabsValues === 'function') {\n setTabsValues((prev) => ({ ...prev, [group!]: tab }));\n }\n };\n\n const contextValue: TabContextType = {\n activeTab: activeTab ?? firstTabValue ?? '',\n setActiveTab: handleSetActiveTab,\n };\n\n return (\n <TabContext.Provider value={contextValue}>\n <div\n className={cn(\n tabContainerVariant({ variant }),\n fullHeight && 'flex h-full flex-col overflow-hidden',\n className\n )}\n {...props}\n >\n {/* Tab Headers */}\n <div className={cn('flex shrink-0 gap-3 p-3', headerClassName)}>\n <TabSelector\n selectedChoice={currentTabValue}\n tabs={tabItems.map((child) => {\n const { label, value, disabled } = child.props;\n const isActive = currentTabValue === value;\n\n return (\n <button\n key={value}\n className={cn(\n 'cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none',\n !isActive && 'text-neutral/70'\n )}\n data-active={isActive}\n disabled={disabled}\n onClick={() => !disabled && handleSetActiveTab(value)}\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={`tabpanel-${value}`}\n id={`tab-${value}`}\n type=\"button\"\n >\n {label ?? value}\n </button>\n );\n })}\n hoverable\n color={TabSelectorColor.TEXT}\n />\n </div>\n {/* Tab Content */}\n {/* Clipper: no overflow; uses clip-path */}\n <div\n className={cn(\n 'relative w-full min-w-0 overflow-x-clip [-webkit-clip-path:inset(0)] [clip-path:inset(0)]',\n fullHeight && 'min-h-0 flex-1'\n )}\n {...containerProps}\n >\n {/* Track */}\n <div\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n className={cn(\n 'grid w-full min-w-0',\n fullHeight && 'h-full',\n isDragging\n ? 'transition-none'\n : 'transition-transform duration-300 ease-in-out'\n )}\n style={{\n gridTemplateColumns: `repeat(${tabItems.length}, 100%)`,\n transform: `translateX(-${activeTabIndex * 100 - (isDragging ? dragDeltaPct : 0)}%)`,\n }}\n >\n {tabItems.map(({ props }, index) => {\n const { value, children, className: itemClassName } = props;\n const isActive = index === activeTabIndex;\n\n return (\n <div\n key={value}\n role=\"tabpanel\"\n aria-labelledby={`tab-${value}`}\n id={`tabpanel-${value}`}\n aria-hidden={!isActive}\n tabIndex={isActive ? 0 : -1}\n data-active={isActive}\n className={cn(\n 'w-full min-w-0 p-6 opacity-100 transition-opacity duration-300 ease-in-out',\n fullHeight && 'h-full overflow-y-auto',\n !isActive && 'pointer-events-none opacity-0', // prevent offscreen interaction\n itemClassName\n )}\n >\n <div\n className={cn(\n 'flex w-full min-w-0 flex-col items-stretch gap-6',\n fullHeight && 'min-h-full'\n )}\n >\n {children}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n </div>\n </TabContext.Provider>\n );\n};\n\n// Create the compound component\nexport const Tab = Object.assign(TabComponent, {\n Item: TabItem,\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Tab.mjs","names":[],"sources":["../../../../src/components/Tab/Tab.tsx"],"sourcesContent":["'use client';\n\nimport { useHorizontalSwipe } from '@hooks/useHorizontalSwipe';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n Children,\n createContext,\n type HTMLAttributes,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useState,\n} from 'react';\nimport { TabSelector, TabSelectorColor } from '../TabSelector';\nimport { useTabContext } from './TabContext';\n\n// Context for managing tab state\ntype TabContextType = {\n activeTab: string;\n setActiveTab: (tab: string) => void;\n};\n\nconst TabContext = createContext<TabContextType | undefined>(undefined);\n\n// Tab container variants\nconst tabContainerVariant = cva('relative w-full rounded-lg', {\n variants: {\n background: {\n with: 'border border-neutral/20 bg-background/2 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur',\n without: '',\n },\n variant: {\n default: '',\n bordered: 'border-2',\n ghost: 'border-0 bg-transparent shadow-none',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n});\n\nexport type TabProps = HTMLAttributes<HTMLDivElement> &\n VariantProps<typeof tabContainerVariant> & {\n defaultTab?: string;\n group?: string;\n children: ReactNode;\n headerClassName?: string;\n fullHeight?: boolean;\n };\n\nexport type TabItemProps = HTMLAttributes<HTMLDivElement> & {\n label: string;\n value: string;\n disabled?: boolean;\n children: ReactNode;\n};\n\n/**\n * TabItem component that represents a single tab\n * Must be used as a child of the Tab component\n */\nconst TabItem = ({ children, ...props }: TabItemProps) => (\n // This component is primarily used for its props by the parent Tab component\n // The actual rendering is handled by the Tab component\n <div {...props}>{children}</div>\n);\n\n// Add display name for better debugging\nTabItem.displayName = 'TabItem';\n\n/**\n * Tab container component that manages tab state and renders tab headers and content\n *\n * Example:\n * ```jsx\n * <Tab defaultTab=\"tab1\">\n * <Tab.Item label=\"First Tab\" value=\"tab1\">\n * Content for first tab\n * </Tab.Item>\n * <Tab.Item label=\"Second Tab\" value=\"tab2\">\n * Content for second tab\n * </Tab.Item>\n * </Tab>\n * ```\n */\nconst TabComponent = ({\n defaultTab,\n group,\n variant,\n children,\n className,\n headerClassName,\n fullHeight,\n ...props\n}: TabProps) => {\n // Extract TabItem children to get their props\n const tabItems = Children.toArray(children).filter((child) => {\n return isValidElement(child) && child.type === TabItem;\n }) as ReactElement<TabItemProps>[];\n\n const firstTabValue = tabItems[0]?.props?.value;\n const { tabsValues, setTabsValues } = useTabContext();\n const [activeTab, setActiveTab] = useState(defaultTab ?? firstTabValue ?? '');\n const hasGroup = group && typeof tabsValues === 'object';\n const currentTabValue =\n (hasGroup ? tabsValues?.[group] : activeTab) ?? defaultTab ?? firstTabValue;\n const activeTabIndex = tabItems.findIndex(\n (tab) => tab.props.value === currentTabValue\n );\n\n const tabsCount = tabItems.length;\n\n const { containerProps, dragDeltaPct, isDragging } = useHorizontalSwipe({\n itemIndex: activeTabIndex,\n itemCount: tabsCount,\n onSwipeLeft: () => {\n const targetIndex = Math.min(tabsCount - 1, activeTabIndex + 1);\n const nextValue = tabItems[targetIndex]?.props?.value;\n if (nextValue) handleSetActiveTab(nextValue);\n },\n onSwipeRight: () => {\n const targetIndex = Math.max(0, activeTabIndex - 1);\n const nextValue = tabItems[targetIndex]?.props?.value;\n if (nextValue) handleSetActiveTab(nextValue);\n },\n });\n\n const handleSetActiveTab = (tab: string) => {\n setActiveTab(tab);\n\n if (typeof setTabsValues === 'function') {\n setTabsValues((prev) => ({ ...prev, [group!]: tab }));\n }\n };\n\n const contextValue: TabContextType = {\n activeTab: activeTab ?? firstTabValue ?? '',\n setActiveTab: handleSetActiveTab,\n };\n\n return (\n <TabContext.Provider value={contextValue}>\n <div\n className={cn(\n tabContainerVariant({ variant }),\n fullHeight && 'flex h-full flex-col overflow-hidden',\n className\n )}\n {...props}\n >\n {/* Tab Headers */}\n <div className={cn('flex shrink-0 gap-3 p-3', headerClassName)}>\n <TabSelector\n selectedChoice={currentTabValue}\n tabs={tabItems.map((child) => {\n const { label, value, disabled } = child.props;\n const isActive = currentTabValue === value;\n\n return (\n <button\n key={value}\n className={cn(\n 'cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none',\n !isActive && 'text-neutral/70'\n )}\n data-active={isActive}\n disabled={disabled}\n onClick={() => !disabled && handleSetActiveTab(value)}\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={`tabpanel-${value}`}\n id={`tab-${value}`}\n type=\"button\"\n >\n {label ?? value}\n </button>\n );\n })}\n hoverable\n color={TabSelectorColor.TEXT}\n />\n </div>\n {/* Tab Content */}\n {/* Clipper: no overflow; uses clip-path */}\n <div\n className={cn(\n 'relative w-full min-w-0 overflow-x-clip [-webkit-clip-path:inset(0)] [clip-path:inset(0)]',\n fullHeight && 'min-h-0 flex-1'\n )}\n {...containerProps}\n >\n {/* Track */}\n <div\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n className={cn(\n 'grid w-full min-w-0',\n fullHeight && 'h-full',\n isDragging\n ? 'transition-none'\n : 'transition-transform duration-300 ease-in-out'\n )}\n style={{\n gridTemplateColumns: `repeat(${tabItems.length}, 100%)`,\n transform: `translateX(-${activeTabIndex * 100 - (isDragging ? dragDeltaPct : 0)}%)`,\n }}\n >\n {tabItems.map(({ props }, index) => {\n const { value, children, className: itemClassName } = props;\n const isActive = index === activeTabIndex;\n\n return (\n <div\n key={value}\n role=\"tabpanel\"\n aria-labelledby={`tab-${value}`}\n id={`tabpanel-${value}`}\n aria-hidden={!isActive}\n tabIndex={isActive ? 0 : -1}\n data-active={isActive}\n className={cn(\n 'w-full min-w-0 p-6 opacity-100 transition-opacity duration-300 ease-in-out',\n fullHeight && 'h-full overflow-y-auto',\n !isActive && 'pointer-events-none opacity-0', // prevent offscreen interaction\n itemClassName\n )}\n >\n <div\n className={cn(\n 'flex w-full min-w-0 flex-col items-stretch gap-6',\n fullHeight && 'min-h-full'\n )}\n >\n {children}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n </div>\n </TabContext.Provider>\n );\n};\n\n// Create the compound component\nexport const Tab = Object.assign(TabComponent, {\n Item: TabItem,\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TechLogo.mjs","names":[],"sources":["../../../../src/components/TechLogo/TechLogo.tsx"],"sourcesContent":["import { type FC, type JSX, lazy, Suspense, type SVGProps } from 'react';\nimport { cn } from '../../utils/cn';\nimport { TechLogoName } from './types';\n\nexport type TechLogoProps = SVGProps<SVGSVGElement> & {\n name: TechLogoName;\n};\n\n/**\n * This ensures React.lazy always receives a valid component.\n */\nconst dynamicLogo = (importFn: () => Promise<any>, exportName: string) =>\n lazy(async () => {\n const module = await importFn();\n const asset = module[exportName];\n\n return { default: asset };\n });\n\nconst logoRecord: Record<TechLogoName, ReturnType<typeof dynamicLogo>> = {\n [TechLogoName.Adonis]: dynamicLogo(\n () => import('./logos/Adonis'),\n 'AdonisLogo'\n ),\n [TechLogoName.Angular]: dynamicLogo(\n () => import('./logos/Angular'),\n 'AngularLogo'\n ),\n [TechLogoName.Astro]: dynamicLogo(() => import('./logos/Astro'), 'AstroLogo'),\n [TechLogoName.Express]: dynamicLogo(\n () => import('./logos/Express'),\n 'ExpressLogo'\n ),\n [TechLogoName.Fastify]: dynamicLogo(\n () => import('./logos/Fastify'),\n 'FastifyLogo'\n ),\n [TechLogoName.Hono]: dynamicLogo(() => import('./logos/Hono'), 'HonoLogo'),\n [TechLogoName.Lynx]: dynamicLogo(() => import('./logos/Lynx'), 'LynxLogo'),\n [TechLogoName.NestJS]: dynamicLogo(\n () => import('./logos/NestJS'),\n 'NestJSLogo'\n ),\n [TechLogoName.Nextjs]: dynamicLogo(\n () => import('./logos/Nextjs'),\n 'NextJSLogo'\n ),\n [TechLogoName.Node]: dynamicLogo(() => import('./logos/Node'), 'NodejsLogo'),\n [TechLogoName.Nuxt]: dynamicLogo(() => import('./logos/Nuxt'), 'NuxtLogo'),\n [TechLogoName.Preact]: dynamicLogo(\n () => import('./logos/Preact'),\n 'PreactLogo'\n ),\n [TechLogoName.React]: dynamicLogo(\n () => import('./logos/Reactjs'),\n 'ReactLogo'\n ),\n [TechLogoName.Solid]: dynamicLogo(() => import('./logos/Solid'), 'SolidLogo'),\n [TechLogoName.Svelte]: dynamicLogo(\n () => import('./logos/Svelte'),\n 'SvelteLogo'\n ),\n [TechLogoName.Tanstack]: dynamicLogo(\n () => import('./logos/Tanstack'),\n 'TanstackLogo'\n ),\n [TechLogoName.Vite]: dynamicLogo(() => import('./logos/Vitejs'), 'ViteLogo'),\n [TechLogoName.Vue]: dynamicLogo(() => import('./logos/Vuejs'), 'VuejsLogo'),\n [TechLogoName.Lit]: dynamicLogo(() => import('./logos/Lit'), 'LitLogo'),\n [TechLogoName.Vanilla]: dynamicLogo(\n () => import('./logos/Vanilla'),\n 'JavaScriptLogo'\n ),\n [TechLogoName.Anthropic]: dynamicLogo(\n () => import('./logos/Anthropic'),\n 'AnthropicLogo'\n ),\n [TechLogoName.Claude]: dynamicLogo(\n () => import('./logos/Claude'),\n 'ClaudeLogo'\n ),\n [TechLogoName.ChatGPT]: dynamicLogo(\n () => import('./logos/ChatGPT'),\n 'ChatGPTLogo'\n ),\n [TechLogoName.DeepSeek]: dynamicLogo(\n () => import('./logos/DeepSeek'),\n 'DeepSeekLogo'\n ),\n [TechLogoName.Gemini]: dynamicLogo(\n () => import('./logos/Gemini'),\n 'GeminiLogo'\n ),\n [TechLogoName.GoogleAI]: dynamicLogo(\n () => import('./logos/GoogleAI'),\n 'GoogleAILogo'\n ),\n [TechLogoName.Grok]: dynamicLogo(() => import('./logos/Grok'), 'GrokLogo'),\n [TechLogoName.Mistral]: dynamicLogo(\n () => import('./logos/Mistral'),\n 'MistralLogo'\n ),\n [TechLogoName.Ollama]: dynamicLogo(\n () => import('./logos/Ollama'),\n 'OllamaLogo'\n ),\n [TechLogoName.OpenAI]: dynamicLogo(\n () => import('./logos/OpenAI'),\n 'OpenAILogo'\n ),\n [TechLogoName.Perplexity]: dynamicLogo(\n () => import('./logos/Perplexity'),\n 'PerplexityLogo'\n ),\n [TechLogoName.GitHub]: dynamicLogo(\n () => import('./logos/GitHub'),\n 'GitHubLogo'\n ),\n [TechLogoName.GitLab]: dynamicLogo(\n () => import('./logos/GitLab'),\n 'GitLabLogo'\n ),\n [TechLogoName.Bitbucket]: dynamicLogo(\n () => import('./logos/Bitbucket'),\n 'BitbucketLogo'\n ),\n [TechLogoName.Google]: dynamicLogo(\n () => import('./logos/Google'),\n 'GoogleLogo'\n ),\n [TechLogoName.LinkedIn]: dynamicLogo(\n () => import('./logos/LinkedIn'),\n 'LinkedInLogo'\n ),\n};\n\nexport const TechLogo: FC<TechLogoProps> = ({\n name,\n ...props\n}): JSX.Element => {\n const LazyLogo = logoRecord[name];\n\n if (!LazyLogo) {\n return <></>;\n }\n\n return (\n <Suspense\n fallback={\n <div className={cn('animate-pulse bg-neutral-200', props.className)} />\n }\n >\n <LazyLogo {...(props as any)} />\n </Suspense>\n );\n};\n"],"mappings":";;;;;;;;;AAWA,MAAM,eAAe,UAA8B,eACjD,KAAK,YAAY;AAIf,QAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"TechLogo.mjs","names":[],"sources":["../../../../src/components/TechLogo/TechLogo.tsx"],"sourcesContent":["import { type FC, type JSX, lazy, Suspense, type SVGProps } from 'react';\nimport { cn } from '../../utils/cn';\nimport { TechLogoName } from './types';\n\nexport type TechLogoProps = SVGProps<SVGSVGElement> & {\n name: TechLogoName;\n};\n\n/**\n * This ensures React.lazy always receives a valid component.\n */\nconst dynamicLogo = (importFn: () => Promise<any>, exportName: string) =>\n lazy(async () => {\n const module = await importFn();\n const asset = module[exportName];\n\n return { default: asset };\n });\n\nconst logoRecord: Record<TechLogoName, ReturnType<typeof dynamicLogo>> = {\n [TechLogoName.Adonis]: dynamicLogo(\n () => import('./logos/Adonis'),\n 'AdonisLogo'\n ),\n [TechLogoName.Angular]: dynamicLogo(\n () => import('./logos/Angular'),\n 'AngularLogo'\n ),\n [TechLogoName.Astro]: dynamicLogo(() => import('./logos/Astro'), 'AstroLogo'),\n [TechLogoName.Express]: dynamicLogo(\n () => import('./logos/Express'),\n 'ExpressLogo'\n ),\n [TechLogoName.Fastify]: dynamicLogo(\n () => import('./logos/Fastify'),\n 'FastifyLogo'\n ),\n [TechLogoName.Hono]: dynamicLogo(() => import('./logos/Hono'), 'HonoLogo'),\n [TechLogoName.Lynx]: dynamicLogo(() => import('./logos/Lynx'), 'LynxLogo'),\n [TechLogoName.NestJS]: dynamicLogo(\n () => import('./logos/NestJS'),\n 'NestJSLogo'\n ),\n [TechLogoName.Nextjs]: dynamicLogo(\n () => import('./logos/Nextjs'),\n 'NextJSLogo'\n ),\n [TechLogoName.Node]: dynamicLogo(() => import('./logos/Node'), 'NodejsLogo'),\n [TechLogoName.Nuxt]: dynamicLogo(() => import('./logos/Nuxt'), 'NuxtLogo'),\n [TechLogoName.Preact]: dynamicLogo(\n () => import('./logos/Preact'),\n 'PreactLogo'\n ),\n [TechLogoName.React]: dynamicLogo(\n () => import('./logos/Reactjs'),\n 'ReactLogo'\n ),\n [TechLogoName.Solid]: dynamicLogo(() => import('./logos/Solid'), 'SolidLogo'),\n [TechLogoName.Svelte]: dynamicLogo(\n () => import('./logos/Svelte'),\n 'SvelteLogo'\n ),\n [TechLogoName.Tanstack]: dynamicLogo(\n () => import('./logos/Tanstack'),\n 'TanstackLogo'\n ),\n [TechLogoName.Vite]: dynamicLogo(() => import('./logos/Vitejs'), 'ViteLogo'),\n [TechLogoName.Vue]: dynamicLogo(() => import('./logos/Vuejs'), 'VuejsLogo'),\n [TechLogoName.Lit]: dynamicLogo(() => import('./logos/Lit'), 'LitLogo'),\n [TechLogoName.Vanilla]: dynamicLogo(\n () => import('./logos/Vanilla'),\n 'JavaScriptLogo'\n ),\n [TechLogoName.Anthropic]: dynamicLogo(\n () => import('./logos/Anthropic'),\n 'AnthropicLogo'\n ),\n [TechLogoName.Claude]: dynamicLogo(\n () => import('./logos/Claude'),\n 'ClaudeLogo'\n ),\n [TechLogoName.ChatGPT]: dynamicLogo(\n () => import('./logos/ChatGPT'),\n 'ChatGPTLogo'\n ),\n [TechLogoName.DeepSeek]: dynamicLogo(\n () => import('./logos/DeepSeek'),\n 'DeepSeekLogo'\n ),\n [TechLogoName.Gemini]: dynamicLogo(\n () => import('./logos/Gemini'),\n 'GeminiLogo'\n ),\n [TechLogoName.GoogleAI]: dynamicLogo(\n () => import('./logos/GoogleAI'),\n 'GoogleAILogo'\n ),\n [TechLogoName.Grok]: dynamicLogo(() => import('./logos/Grok'), 'GrokLogo'),\n [TechLogoName.Mistral]: dynamicLogo(\n () => import('./logos/Mistral'),\n 'MistralLogo'\n ),\n [TechLogoName.Ollama]: dynamicLogo(\n () => import('./logos/Ollama'),\n 'OllamaLogo'\n ),\n [TechLogoName.OpenAI]: dynamicLogo(\n () => import('./logos/OpenAI'),\n 'OpenAILogo'\n ),\n [TechLogoName.Perplexity]: dynamicLogo(\n () => import('./logos/Perplexity'),\n 'PerplexityLogo'\n ),\n [TechLogoName.GitHub]: dynamicLogo(\n () => import('./logos/GitHub'),\n 'GitHubLogo'\n ),\n [TechLogoName.GitLab]: dynamicLogo(\n () => import('./logos/GitLab'),\n 'GitLabLogo'\n ),\n [TechLogoName.Bitbucket]: dynamicLogo(\n () => import('./logos/Bitbucket'),\n 'BitbucketLogo'\n ),\n [TechLogoName.Google]: dynamicLogo(\n () => import('./logos/Google'),\n 'GoogleLogo'\n ),\n [TechLogoName.LinkedIn]: dynamicLogo(\n () => import('./logos/LinkedIn'),\n 'LinkedInLogo'\n ),\n};\n\nexport const TechLogo: FC<TechLogoProps> = ({\n name,\n ...props\n}): JSX.Element => {\n const LazyLogo = logoRecord[name];\n\n if (!LazyLogo) {\n return <></>;\n }\n\n return (\n <Suspense\n fallback={\n <div className={cn('animate-pulse bg-neutral-200', props.className)} />\n }\n >\n <LazyLogo {...(props as any)} />\n </Suspense>\n );\n};\n"],"mappings":";;;;;;;;;AAWA,MAAM,eAAe,UAA8B,eACjD,KAAK,YAAY;AAIf,QAAO,EAAE,UAFK,MADO,UAAU,EACV,aAEI;EACzB;AAEJ,MAAM,aAAmE;aAChD,kBACf,OAAO,uBACb,aACD;cACuB,kBAChB,OAAO,wBACb,cACD;YACqB,kBAAkB,OAAO,sBAAkB,YAAY;cACrD,kBAChB,OAAO,wBACb,cACD;cACuB,kBAChB,OAAO,wBACb,cACD;WACoB,kBAAkB,OAAO,qBAAiB,WAAW;WACrD,kBAAkB,OAAO,qBAAiB,WAAW;aACnD,kBACf,OAAO,uBACb,aACD;aACsB,kBACf,OAAO,uBACb,aACD;WACoB,kBAAkB,OAAO,qBAAiB,aAAa;WACvD,kBAAkB,OAAO,qBAAiB,WAAW;aACnD,kBACf,OAAO,uBACb,aACD;YACqB,kBACd,OAAO,wBACb,YACD;YACqB,kBAAkB,OAAO,sBAAkB,YAAY;aACtD,kBACf,OAAO,uBACb,aACD;eACwB,kBACjB,OAAO,yBACb,eACD;WACoB,kBAAkB,OAAO,uBAAmB,WAAW;UACxD,kBAAkB,OAAO,sBAAkB,YAAY;UACvD,kBAAkB,OAAO,oBAAgB,UAAU;cAC/C,kBAChB,OAAO,wBACb,iBACD;gBACyB,kBAClB,OAAO,0BACb,gBACD;aACsB,kBACf,OAAO,uBACb,aACD;cACuB,kBAChB,OAAO,wBACb,cACD;eACwB,kBACjB,OAAO,yBACb,eACD;aACsB,kBACf,OAAO,uBACb,aACD;eACwB,kBACjB,OAAO,yBACb,eACD;WACoB,kBAAkB,OAAO,qBAAiB,WAAW;cAClD,kBAChB,OAAO,wBACb,cACD;aACsB,kBACf,OAAO,uBACb,aACD;aACsB,kBACf,OAAO,uBACb,aACD;iBAC0B,kBACnB,OAAO,2BACb,iBACD;aACsB,kBACf,OAAO,uBACb,aACD;aACsB,kBACf,OAAO,uBACb,aACD;gBACyB,kBAClB,OAAO,0BACb,gBACD;aACsB,kBACf,OAAO,uBACb,aACD;eACwB,kBACjB,OAAO,yBACb,eACD;CACF;AAED,MAAa,YAA+B,EAC1C,MACA,GAAG,YACc;CACjB,MAAM,WAAW,WAAW;AAE5B,KAAI,CAAC,SACH,QAAO,kCAAK;AAGd,QACE,oBAAC,UAAD;EACE,UACE,oBAAC,OAAD,EAAK,WAAW,GAAG,gCAAgC,MAAM,UAAU,EAAI;YAGzE,oBAAC,UAAD,EAAU,GAAK,OAAiB;EACvB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompleteTextArea.mjs","names":[],"sources":["../../../../src/components/TextArea/AutocompleteTextArea.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ChangeEvent,\n type FC,\n type KeyboardEvent,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport type { AutoSizedTextAreaProps } from './AutoSizeTextArea';\nimport {\n ContentEditableTextArea,\n type ContentEditableTextAreaHandle,\n} from './ContentEditableTextArea';\n\n/**\n * Props for the AutocompleteTextArea component.\n *\n * Extends AutoSizedTextAreaProps with inline autocomplete functionality\n * using a contentEditable-based textarea.\n *\n * @example\n * ```tsx\n * <AutoCompleteTextarea\n * placeholder=\"Start typing...\"\n * isActive={true}\n * autoSize={true}\n * maxRows={10}\n * />\n * ```\n */\nexport type AutocompleteTextAreaProps = AutoSizedTextAreaProps & {\n /** Whether inline autocomplete ghost text is active */\n isActive?: boolean;\n /** Manual suggestion text to display as ghost text after the cursor */\n suggestion?: string;\n};\n\n/**\n * AutoCompleteTextarea Component\n *\n * A textarea with inline autocomplete ghost text, built on a contentEditable div\n * instead of a native `<textarea>`. Ghost text (suggestions) is rendered inline\n * at the cursor position and can be accepted with the Tab key.\n *\n * The component wraps `ContentEditableTextArea` and manages suggestion state.\n * When `suggestion` prop is provided it is shown as ghost text at the end of the\n * current text. When `isActive` is false, ghost text is hidden.\n *\n * @example\n * ```tsx\n * <AutoCompleteTextarea\n * value={content}\n * onChange={handleChange}\n * suggestion=\"suggested completion...\"\n * isActive={true}\n * autoSize={true}\n * />\n * ```\n */\nexport const AutoCompleteTextarea: FC<AutocompleteTextAreaProps> = ({\n isActive = true,\n suggestion: suggestionProp,\n ...props\n}) => {\n const defaultValue = String(props.value ?? props.defaultValue ?? '');\n const [text, setText] = useState(defaultValue);\n const [suggestion, setSuggestion] = useState('');\n const editorRef = useRef<ContentEditableTextAreaHandle>(null);\n\n useEffect(() => {\n if (typeof props.value === 'undefined') return;\n setText(String(props.value ?? props.defaultValue ?? ''));\n }, [props.value, props.defaultValue]);\n\n const acceptSuggestion = () => {\n const active = suggestionProp ?? suggestion;\n if (!active) return;\n\n const cursor = editorRef.current?.getCursorOffset() ?? text.length;\n const next = text.slice(0, cursor) + active + text.slice(cursor);\n setText(next);\n setSuggestion('');\n\n setTimeout(() => {\n editorRef.current?.focus();\n editorRef.current?.setCursorAtOffset(cursor + active.length);\n }, 0);\n };\n\n const activeGhost = isActive\n ? (suggestionProp ?? (suggestion || undefined))\n : undefined;\n const textLines = text.split('\\n');\n const activeLine = suggestionProp ? textLines.length - 1 : undefined;\n const activeOffset = suggestionProp\n ? (textLines[textLines.length - 1]?.length ?? 0)\n : undefined;\n\n return (\n <ContentEditableTextArea\n ref={editorRef}\n value={text}\n onChange={(val) => {\n setText(val);\n setSuggestion('');\n\n if (props.onChange) {\n const evt = {\n target: { value: val },\n currentTarget: { value: val },\n } as ChangeEvent<HTMLTextAreaElement>;\n props.onChange(evt);\n }\n }}\n onKeyDown={(e) => {\n if (e.key === 'Tab' && (suggestionProp ?? suggestion)) {\n e.preventDefault();\n acceptSuggestion();\n }\n props.onKeyDown?.(e as unknown as KeyboardEvent<HTMLTextAreaElement>);\n }}\n ghostText={activeGhost}\n ghostLine={activeLine}\n ghostOffset={activeOffset}\n placeholder={props.placeholder}\n disabled={props.disabled}\n autoSize={props.autoSize}\n maxRows={props.maxRows}\n minRows={props.rows}\n variant={props.variant}\n validationStyleEnabled={props.validationStyleEnabled}\n className={props.className}\n dir={props.dir as 'ltr' | 'rtl' | 'auto'}\n aria-label={props['aria-label']}\n aria-invalid={props['aria-invalid']}\n aria-describedby={props['aria-describedby']}\n data-testid={(props as Record<string, unknown>)['data-testid'] as string}\n />\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,MAAa,wBAAuD,EAClE,WAAW,MACX,YAAY,gBACZ,GAAG,YACC;CAEJ,MAAM,CAAC,MAAM,WAAW,SADH,OAAO,MAAM,SAAS,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"AutocompleteTextArea.mjs","names":[],"sources":["../../../../src/components/TextArea/AutocompleteTextArea.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ChangeEvent,\n type FC,\n type KeyboardEvent,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport type { AutoSizedTextAreaProps } from './AutoSizeTextArea';\nimport {\n ContentEditableTextArea,\n type ContentEditableTextAreaHandle,\n} from './ContentEditableTextArea';\n\n/**\n * Props for the AutocompleteTextArea component.\n *\n * Extends AutoSizedTextAreaProps with inline autocomplete functionality\n * using a contentEditable-based textarea.\n *\n * @example\n * ```tsx\n * <AutoCompleteTextarea\n * placeholder=\"Start typing...\"\n * isActive={true}\n * autoSize={true}\n * maxRows={10}\n * />\n * ```\n */\nexport type AutocompleteTextAreaProps = AutoSizedTextAreaProps & {\n /** Whether inline autocomplete ghost text is active */\n isActive?: boolean;\n /** Manual suggestion text to display as ghost text after the cursor */\n suggestion?: string;\n};\n\n/**\n * AutoCompleteTextarea Component\n *\n * A textarea with inline autocomplete ghost text, built on a contentEditable div\n * instead of a native `<textarea>`. Ghost text (suggestions) is rendered inline\n * at the cursor position and can be accepted with the Tab key.\n *\n * The component wraps `ContentEditableTextArea` and manages suggestion state.\n * When `suggestion` prop is provided it is shown as ghost text at the end of the\n * current text. When `isActive` is false, ghost text is hidden.\n *\n * @example\n * ```tsx\n * <AutoCompleteTextarea\n * value={content}\n * onChange={handleChange}\n * suggestion=\"suggested completion...\"\n * isActive={true}\n * autoSize={true}\n * />\n * ```\n */\nexport const AutoCompleteTextarea: FC<AutocompleteTextAreaProps> = ({\n isActive = true,\n suggestion: suggestionProp,\n ...props\n}) => {\n const defaultValue = String(props.value ?? props.defaultValue ?? '');\n const [text, setText] = useState(defaultValue);\n const [suggestion, setSuggestion] = useState('');\n const editorRef = useRef<ContentEditableTextAreaHandle>(null);\n\n useEffect(() => {\n if (typeof props.value === 'undefined') return;\n setText(String(props.value ?? props.defaultValue ?? ''));\n }, [props.value, props.defaultValue]);\n\n const acceptSuggestion = () => {\n const active = suggestionProp ?? suggestion;\n if (!active) return;\n\n const cursor = editorRef.current?.getCursorOffset() ?? text.length;\n const next = text.slice(0, cursor) + active + text.slice(cursor);\n setText(next);\n setSuggestion('');\n\n setTimeout(() => {\n editorRef.current?.focus();\n editorRef.current?.setCursorAtOffset(cursor + active.length);\n }, 0);\n };\n\n const activeGhost = isActive\n ? (suggestionProp ?? (suggestion || undefined))\n : undefined;\n const textLines = text.split('\\n');\n const activeLine = suggestionProp ? textLines.length - 1 : undefined;\n const activeOffset = suggestionProp\n ? (textLines[textLines.length - 1]?.length ?? 0)\n : undefined;\n\n return (\n <ContentEditableTextArea\n ref={editorRef}\n value={text}\n onChange={(val) => {\n setText(val);\n setSuggestion('');\n\n if (props.onChange) {\n const evt = {\n target: { value: val },\n currentTarget: { value: val },\n } as ChangeEvent<HTMLTextAreaElement>;\n props.onChange(evt);\n }\n }}\n onKeyDown={(e) => {\n if (e.key === 'Tab' && (suggestionProp ?? suggestion)) {\n e.preventDefault();\n acceptSuggestion();\n }\n props.onKeyDown?.(e as unknown as KeyboardEvent<HTMLTextAreaElement>);\n }}\n ghostText={activeGhost}\n ghostLine={activeLine}\n ghostOffset={activeOffset}\n placeholder={props.placeholder}\n disabled={props.disabled}\n autoSize={props.autoSize}\n maxRows={props.maxRows}\n minRows={props.rows}\n variant={props.variant}\n validationStyleEnabled={props.validationStyleEnabled}\n className={props.className}\n dir={props.dir as 'ltr' | 'rtl' | 'auto'}\n aria-label={props['aria-label']}\n aria-invalid={props['aria-invalid']}\n aria-describedby={props['aria-describedby']}\n data-testid={(props as Record<string, unknown>)['data-testid'] as string}\n />\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,MAAa,wBAAuD,EAClE,WAAW,MACX,YAAY,gBACZ,GAAG,YACC;CAEJ,MAAM,CAAC,MAAM,WAAW,SADH,OAAO,MAAM,SAAS,MAAM,gBAAgB,GACpB,CAAC;CAC9C,MAAM,CAAC,YAAY,iBAAiB,SAAS,GAAG;CAChD,MAAM,YAAY,OAAsC,KAAK;AAE7D,iBAAgB;AACd,MAAI,OAAO,MAAM,UAAU,YAAa;AACxC,UAAQ,OAAO,MAAM,SAAS,MAAM,gBAAgB,GAAG,CAAC;IACvD,CAAC,MAAM,OAAO,MAAM,aAAa,CAAC;CAErC,MAAM,yBAAyB;EAC7B,MAAM,SAAS,kBAAkB;AACjC,MAAI,CAAC,OAAQ;EAEb,MAAM,SAAS,UAAU,SAAS,iBAAiB,IAAI,KAAK;AAE5D,UADa,KAAK,MAAM,GAAG,OAAO,GAAG,SAAS,KAAK,MAAM,OAAO,CACnD;AACb,gBAAc,GAAG;AAEjB,mBAAiB;AACf,aAAU,SAAS,OAAO;AAC1B,aAAU,SAAS,kBAAkB,SAAS,OAAO,OAAO;KAC3D,EAAE;;CAGP,MAAM,cAAc,WACf,mBAAmB,cAAc,UAClC;CACJ,MAAM,YAAY,KAAK,MAAM,KAAK;AAMlC,QACE,oBAAC,yBAAD;EACE,KAAK;EACL,OAAO;EACP,WAAW,QAAQ;AACjB,WAAQ,IAAI;AACZ,iBAAc,GAAG;AAEjB,OAAI,MAAM,UAAU;IAClB,MAAM,MAAM;KACV,QAAQ,EAAE,OAAO,KAAK;KACtB,eAAe,EAAE,OAAO,KAAK;KAC9B;AACD,UAAM,SAAS,IAAI;;;EAGvB,YAAY,MAAM;AAChB,OAAI,EAAE,QAAQ,UAAU,kBAAkB,aAAa;AACrD,MAAE,gBAAgB;AAClB,sBAAkB;;AAEpB,SAAM,YAAY,EAAmD;;EAEvE,WAAW;EACX,WA7Be,iBAAiB,UAAU,SAAS,IAAI;EA8BvD,aA7BiB,iBAChB,UAAU,UAAU,SAAS,IAAI,UAAU,IAC5C;EA4BA,aAAa,MAAM;EACnB,UAAU,MAAM;EAChB,UAAU,MAAM;EAChB,SAAS,MAAM;EACf,SAAS,MAAM;EACf,SAAS,MAAM;EACf,wBAAwB,MAAM;EAC9B,WAAW,MAAM;EACjB,KAAK,MAAM;EACX,cAAY,MAAM;EAClB,gBAAc,MAAM;EACpB,oBAAkB,MAAM;EACxB,eAAc,MAAkC;EAChD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContentEditableTextArea.mjs","names":[],"sources":["../../../../src/components/TextArea/ContentEditableTextArea.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport type { VariantProps } from 'class-variance-authority';\nimport {\n type ClipboardEvent,\n type DragEvent,\n type FC,\n type HTMLAttributes,\n type InputEvent,\n type KeyboardEvent,\n type MutableRefObject,\n type Ref,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react';\nimport { type InputVariant, inputVariants } from '../Input';\n\ntype CaretPosition = {\n line: number;\n offset: number;\n};\n\ntype UseContentEditableOptions = {\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n disabled?: boolean;\n};\n\nconst ZERO_WIDTH_SPACE = '\\u200B';\n\nconst getTextFromContainer = (container: HTMLDivElement): string => {\n const lineEls = container.querySelectorAll('[data-line]');\n if (lineEls.length === 0) {\n return (container.textContent ?? '').split(ZERO_WIDTH_SPACE).join('');\n }\n\n return Array.from(lineEls)\n .map((el) => {\n const editable = el.querySelector('[data-editable]');\n const raw = editable?.textContent ?? el.textContent ?? '';\n return raw === ZERO_WIDTH_SPACE\n ? ''\n : raw.split(ZERO_WIDTH_SPACE).join('');\n })\n .join('\\n');\n};\n\nconst splitLines = (text: string): string[] => {\n const lines = text.split('\\n');\n return lines.length === 0 ? [''] : lines;\n};\n\n// Cached Intl.Segmenter for grapheme-aware deletion (emoji, CJK, etc.)\n// Intl.Segmenter is ES2022+ but we feature-detect at runtime.\ntype GraphemeSegmenter = {\n segment: (input: string) => Iterable<{ segment: string }>;\n};\n\nconst createGraphemeSegmenter = (): GraphemeSegmenter | null => {\n if (typeof Intl === 'undefined' || !('Segmenter' in Intl)) return null;\n const SegmenterCtor = (\n Intl as unknown as Record<\n string,\n new (\n ...args: unknown[]\n ) => GraphemeSegmenter\n >\n ).Segmenter;\n return new SegmenterCtor(undefined, { granularity: 'grapheme' });\n};\n\nconst graphemeSegmenter = createGraphemeSegmenter();\n\n/**\n * Find the previous grapheme cluster boundary for safe deletion.\n * Falls back to code-point-aware deletion if Intl.Segmenter is unavailable.\n */\nconst prevGraphemeBoundary = (text: string, offset: number): number => {\n if (offset <= 0) return 0;\n\n if (graphemeSegmenter) {\n const segments = [...graphemeSegmenter.segment(text.slice(0, offset))];\n const last = segments[segments.length - 1];\n return last ? offset - last.segment.length : offset - 1;\n }\n\n // Fallback: handle surrogate pairs\n const code = text.charCodeAt(offset - 1);\n if (code >= 0xdc00 && code <= 0xdfff && offset >= 2) {\n return offset - 2;\n }\n return offset - 1;\n};\n\n/**\n * Find the next grapheme cluster boundary for safe forward deletion.\n */\nconst nextGraphemeBoundary = (text: string, offset: number): number => {\n if (offset >= text.length) return text.length;\n\n if (graphemeSegmenter) {\n const segments = [...graphemeSegmenter.segment(text.slice(offset))];\n const first = segments[0];\n return first ? offset + first.segment.length : offset + 1;\n }\n\n // Fallback: handle surrogate pairs\n const code = text.charCodeAt(offset);\n if (code >= 0xd800 && code <= 0xdbff && offset + 1 < text.length) {\n return offset + 2;\n }\n return offset + 1;\n};\n\n/**\n * Find the previous word boundary for Option+Backspace.\n */\nconst prevWordBoundary = (text: string, offset: number): number => {\n if (offset <= 0) return 0;\n let i = offset - 1;\n // Skip whitespace\n while (i > 0 && /\\s/.test(text[i - 1])) i--;\n // Skip word characters\n while (i > 0 && /\\S/.test(text[i - 1])) i--;\n return i;\n};\n\n/**\n * Find the next word boundary for Option+Delete.\n */\nconst nextWordBoundary = (text: string, offset: number): number => {\n if (offset >= text.length) return text.length;\n let i = offset;\n // Skip word characters\n while (i < text.length && /\\S/.test(text[i])) i++;\n // Skip whitespace\n while (i < text.length && /\\s/.test(text[i])) i++;\n return i;\n};\n\n/**\n * Find the start of the current line (for Cmd+Backspace).\n */\nconst lineStart = (text: string, offset: number): number => {\n const before = text.slice(0, offset);\n const lastNewline = before.lastIndexOf('\\n');\n return lastNewline + 1;\n};\n\n/**\n * Find the end of the current line (for Cmd+Delete).\n */\nconst lineEnd = (text: string, offset: number): number => {\n const nextNewline = text.indexOf('\\n', offset);\n return nextNewline === -1 ? text.length : nextNewline;\n};\n\nexport const useContentEditable = ({\n value,\n defaultValue,\n onChange,\n disabled = false,\n}: UseContentEditableOptions) => {\n const initialValue = value ?? defaultValue ?? '';\n const [lines, setLines] = useState<string[]>(() => splitLines(initialValue));\n const containerRef = useRef<HTMLDivElement | null>(null);\n const pendingCaretRef = useRef<CaretPosition | null>(null);\n const isControlled = value !== undefined;\n\n // Keep a ref to the latest lines to avoid stale closures in rapid typing\n const linesRef = useRef(lines);\n linesRef.current = lines;\n\n useEffect(() => {\n if (isControlled && value !== undefined) {\n setLines(splitLines(value));\n }\n }, [value, isControlled]);\n\n const getText = () => linesRef.current.join('\\n');\n\n const getCaretPosition = (): CaretPosition | null => {\n const sel = window.getSelection();\n if (!sel?.rangeCount || !containerRef.current) return null;\n\n const range = sel.getRangeAt(0);\n const lineEls = containerRef.current.querySelectorAll('[data-line]');\n\n for (let i = 0; i < lineEls.length; i++) {\n if (lineEls[i].contains(range.startContainer)) {\n return { line: i, offset: range.startOffset };\n }\n }\n return null;\n };\n\n const getSelectionOffsets = (): {\n start: number;\n end: number;\n hasSelection: boolean;\n } | null => {\n const sel = window.getSelection();\n if (!sel?.rangeCount || !containerRef.current) return null;\n\n const range = sel.getRangeAt(0);\n const lineEls = containerRef.current.querySelectorAll('[data-line]');\n const currentLines = linesRef.current;\n\n const findOffset = (node: Node, nodeOffset: number): number => {\n for (let i = 0; i < lineEls.length; i++) {\n if (lineEls[i].contains(node)) {\n let flat = 0;\n for (let j = 0; j < i; j++) {\n flat += currentLines[j].length + 1;\n }\n return flat + Math.min(nodeOffset, currentLines[i]?.length ?? 0);\n }\n }\n // Selection is on the root container (e.g. select-all)\n if (node === containerRef.current) {\n if (nodeOffset === 0) return 0;\n return currentLines.join('\\n').length;\n }\n return 0;\n };\n\n const start = findOffset(range.startContainer, range.startOffset);\n const end = findOffset(range.endContainer, range.endOffset);\n\n return {\n start: Math.min(start, end),\n end: Math.max(start, end),\n hasSelection: !range.collapsed,\n };\n };\n\n const setCaretPosition = (pos: CaretPosition) => {\n if (!containerRef.current) return;\n\n const lineEls = containerRef.current.querySelectorAll('[data-line]');\n const lineEl = lineEls[pos.line];\n if (!lineEl) return;\n\n const editable = lineEl.querySelector('[data-editable]');\n const node =\n editable?.firstChild ?? editable ?? lineEl.firstChild ?? lineEl;\n\n const sel = window.getSelection();\n if (!sel) return;\n\n const range = document.createRange();\n const maxOff = Math.min(pos.offset, node.textContent?.length ?? 0);\n\n try {\n range.setStart(node, maxOff);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n } catch {\n range.selectNodeContents(node);\n range.collapse(false);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n };\n\n useEffect(() => {\n if (pendingCaretRef.current && containerRef.current) {\n setCaretPosition(pendingCaretRef.current);\n pendingCaretRef.current = null;\n }\n });\n\n const flatOffsetFromCaret = (pos: CaretPosition): number => {\n const currentLines = linesRef.current;\n let offset = 0;\n for (let i = 0; i < pos.line; i++) {\n offset += currentLines[i].length + 1;\n }\n return offset + pos.offset;\n };\n\n const caretFromFlatOffset = (\n flat: number,\n targetLines: string[]\n ): CaretPosition => {\n let rem = flat;\n for (let i = 0; i < targetLines.length; i++) {\n if (rem <= targetLines[i].length) {\n return { line: i, offset: rem };\n }\n rem -= targetLines[i].length + 1;\n }\n return {\n line: targetLines.length - 1,\n offset: targetLines[targetLines.length - 1]?.length ?? 0,\n };\n };\n\n const getCursorOffset = (): number => {\n const pos = getCaretPosition();\n if (!pos) return 0;\n return flatOffsetFromCaret(pos);\n };\n\n /**\n * Applies a text mutation: computes new lines, sets pending caret, updates state.\n */\n const applyTextChange = (newText: string, caretOffset: number) => {\n const newLines = splitLines(newText);\n pendingCaretRef.current = caretFromFlatOffset(caretOffset, newLines);\n setLines(newLines);\n onChange?.(newText);\n };\n\n const handleInput = () => {\n if (pendingCaretRef.current !== null) return;\n if (disabled || !containerRef.current) return;\n\n const caretPos = getCaretPosition();\n const newText = getTextFromContainer(containerRef.current);\n const newLines = splitLines(newText);\n\n pendingCaretRef.current = caretPos;\n setLines(newLines);\n onChange?.(newText);\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (disabled) {\n e.preventDefault();\n return;\n }\n\n // Don't intercept during IME composition (CJK input)\n if (e.nativeEvent.isComposing) return;\n\n // Block undo/redo - browser would mutate DOM out of sync with React\n if ((e.metaKey || e.ctrlKey) && e.key === 'z') {\n e.preventDefault();\n return;\n }\n\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n\n if (e.key === 'Enter') {\n e.preventDefault();\n const newText =\n currentText.slice(0, selInfo.start) +\n '\\n' +\n currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start + 1);\n return;\n }\n\n if (e.key === 'Backspace') {\n e.preventDefault();\n\n if (selInfo.hasSelection) {\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start);\n } else {\n if (selInfo.start === 0) return;\n\n let deleteFrom: number;\n if (e.metaKey) {\n // Cmd+Backspace: delete to start of line\n deleteFrom = lineStart(currentText, selInfo.start);\n } else if (e.altKey) {\n // Option+Backspace: delete previous word\n deleteFrom = prevWordBoundary(currentText, selInfo.start);\n } else {\n // Regular backspace: delete one grapheme\n deleteFrom = prevGraphemeBoundary(currentText, selInfo.start);\n }\n\n const newText =\n currentText.slice(0, deleteFrom) + currentText.slice(selInfo.start);\n applyTextChange(newText, deleteFrom);\n }\n return;\n }\n\n if (e.key === 'Delete') {\n e.preventDefault();\n\n if (selInfo.hasSelection) {\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start);\n } else {\n if (selInfo.start >= currentText.length) return;\n\n let deleteTo: number;\n if (e.metaKey) {\n // Cmd+Delete: delete to end of line\n deleteTo = lineEnd(currentText, selInfo.start);\n } else if (e.altKey) {\n // Option+Delete: delete next word\n deleteTo = nextWordBoundary(currentText, selInfo.start);\n } else {\n // Regular delete: delete one grapheme\n deleteTo = nextGraphemeBoundary(currentText, selInfo.start);\n }\n\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(deleteTo);\n applyTextChange(newText, selInfo.start);\n }\n return;\n }\n };\n\n const handleCut = (e: ClipboardEvent<HTMLDivElement>) => {\n if (disabled) {\n e.preventDefault();\n return;\n }\n\n e.preventDefault();\n const selInfo = getSelectionOffsets();\n if (!selInfo?.hasSelection) return;\n\n const currentText = linesRef.current.join('\\n');\n const selectedText = currentText.slice(selInfo.start, selInfo.end);\n\n // Write selected text to clipboard\n e.clipboardData.setData('text/plain', selectedText);\n\n // Delete the selected text\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start);\n };\n\n const handlePaste = (e: ClipboardEvent<HTMLDivElement>) => {\n if (disabled) {\n e.preventDefault();\n return;\n }\n\n e.preventDefault();\n const pastedText = e.clipboardData.getData('text/plain');\n if (!pastedText) return;\n\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n const newText =\n currentText.slice(0, selInfo.start) +\n pastedText +\n currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start + pastedText.length);\n };\n\n const handleBeforeInput = (e: InputEvent<HTMLDivElement>) => {\n if (disabled) return;\n\n const inputEvent = e.nativeEvent as InputEvent;\n\n // Don't intercept during IME composition (CJK input)\n if (inputEvent.isComposing) return;\n\n const inputType = inputEvent.inputType;\n\n // Skip types handled by handleKeyDown (when keydown fires)\n if (inputType === 'insertParagraph' || inputType === 'insertLineBreak') {\n return;\n }\n\n // Handle deletions as fallback for mobile keyboards that don't fire keydown\n if (\n inputType === 'deleteContentBackward' ||\n inputType === 'deleteContentForward'\n ) {\n e.preventDefault();\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n\n if (selInfo.hasSelection) {\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start);\n } else if (inputType === 'deleteContentBackward') {\n if (selInfo.start === 0) return;\n const deleteFrom = prevGraphemeBoundary(currentText, selInfo.start);\n const newText =\n currentText.slice(0, deleteFrom) + currentText.slice(selInfo.start);\n applyTextChange(newText, deleteFrom);\n } else {\n if (selInfo.start >= currentText.length) return;\n const deleteTo = nextGraphemeBoundary(currentText, selInfo.start);\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(deleteTo);\n applyTextChange(newText, selInfo.start);\n }\n return;\n }\n\n // Handle spell-check replacements\n if (inputType === 'insertReplacementText') {\n e.preventDefault();\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n const replacement =\n inputEvent.data ?? inputEvent.dataTransfer?.getData('text/plain') ?? '';\n const newText =\n currentText.slice(0, selInfo.start) +\n replacement +\n currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start + replacement.length);\n return;\n }\n\n if (inputType === 'insertText' && inputEvent.data) {\n e.preventDefault();\n\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n const inserted = inputEvent.data;\n const newText =\n currentText.slice(0, selInfo.start) +\n inserted +\n currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start + inserted.length);\n }\n };\n\n const handleDrop = (e: DragEvent<HTMLDivElement>) => {\n // Block drag-and-drop to prevent uncontrolled DOM mutations\n e.preventDefault();\n };\n\n const handleDragOver = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n };\n\n return {\n lines,\n containerRef,\n getText,\n handleInput,\n handleBeforeInput,\n handleKeyDown,\n handleCut,\n handlePaste,\n handleDrop,\n handleDragOver,\n getCaretPosition,\n setCaretPosition,\n getCursorOffset,\n caretFromFlatOffset,\n };\n};\n\ntype LineProps = {\n index: number;\n text: string;\n isLast: boolean;\n ghostText?: string;\n};\n\nconst Line: FC<LineProps> = ({ index, text, isLast, ghostText }) => (\n <span data-line={index} className=\"block min-h-[1.5rem]\">\n <span data-editable>{text || '\\u200B'}</span>\n {ghostText && (\n <span\n data-ghost\n className=\"pointer-events-none select-none text-neutral\"\n aria-hidden=\"true\"\n >\n {ghostText}\n </span>\n )}\n {!isLast && <br />}\n </span>\n);\n\nexport type ContentEditableTextAreaHandle = {\n getContainer: () => HTMLDivElement | null;\n getText: () => string;\n focus: () => void;\n getCursorOffset: () => number;\n setCursorAtOffset: (offset: number) => void;\n};\n\nexport type ContentEditableTextAreaProps = Omit<\n HTMLAttributes<HTMLDivElement>,\n 'onChange' | 'defaultValue'\n> & {\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n placeholder?: string;\n disabled?: boolean;\n minRows?: number;\n maxRows?: number;\n autoSize?: boolean;\n validationStyleEnabled?: boolean;\n variant?: InputVariant | `${InputVariant}`;\n ghostText?: string;\n ghostLine?: number;\n ghostOffset?: number;\n ref?: Ref<ContentEditableTextAreaHandle>;\n dir?: 'ltr' | 'rtl' | 'auto';\n} & Omit<\n VariantProps<typeof inputVariants>,\n 'validationStyleEnabled' | 'variant'\n >;\n\nconst LINE_HEIGHT = 24;\nconst LINE_PADDING = 12;\n\nexport const ContentEditableTextArea: FC<ContentEditableTextAreaProps> = ({\n value,\n defaultValue,\n onChange,\n placeholder,\n disabled = false,\n minRows = 1,\n maxRows = 999,\n autoSize = true,\n validationStyleEnabled = false,\n variant,\n ghostText,\n ghostLine,\n ghostOffset,\n onClick,\n className,\n dir = 'auto',\n ref,\n ...rest\n}) => {\n const {\n lines,\n containerRef,\n getText,\n handleInput,\n handleBeforeInput,\n handleKeyDown,\n handleCut,\n handlePaste,\n handleDrop,\n handleDragOver,\n getCursorOffset,\n setCaretPosition,\n caretFromFlatOffset,\n } = useContentEditable({ value, defaultValue, onChange, disabled });\n\n const elRef = useRef<HTMLDivElement | null>(null);\n\n const setRef = (el: HTMLDivElement | null) => {\n elRef.current = el;\n (containerRef as MutableRefObject<HTMLDivElement | null>).current = el;\n };\n\n useImperativeHandle(ref, () => ({\n getContainer: () => elRef.current,\n getText,\n focus: () => elRef.current?.focus(),\n getCursorOffset,\n setCursorAtOffset: (offset: number) => {\n setCaretPosition(caretFromFlatOffset(offset, lines));\n },\n }));\n\n useEffect(() => {\n if (!autoSize || !elRef.current) return;\n\n const el = elRef.current;\n const max = LINE_HEIGHT * maxRows + LINE_PADDING;\n const min = LINE_HEIGHT * minRows + LINE_PADDING;\n\n el.style.height = 'auto';\n const sh = el.scrollHeight;\n el.style.height = `${Math.max(Math.min(sh, max), min)}px`;\n el.style.overflowY = sh > max ? 'auto' : 'hidden';\n }, [lines, autoSize, maxRows, minRows]);\n\n const isEmpty = lines.length === 1 && lines[0] === '';\n const hasGhost =\n ghostText && ghostLine !== undefined && ghostOffset !== undefined;\n\n return (\n <div className=\"relative w-full\">\n {isEmpty && placeholder && (\n <div\n className=\"pointer-events-none absolute inset-0 select-none px-2 py-3 text-base text-neutral-400 leading-[1.5rem] md:py-2 md:text-sm\"\n aria-hidden=\"true\"\n >\n {placeholder}\n </div>\n )}\n\n <div\n {...rest}\n ref={setRef}\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-placeholder={placeholder}\n aria-disabled={disabled}\n aria-autocomplete={hasGhost ? 'inline' : undefined}\n tabIndex={disabled ? -1 : 0}\n contentEditable={!disabled}\n suppressContentEditableWarning\n dir={dir}\n onInput={handleInput}\n onBeforeInput={handleBeforeInput}\n onKeyDown={handleKeyDown}\n onCut={handleCut}\n onPaste={handlePaste}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onClick={onClick}\n className={cn(\n 'resize-none whitespace-pre-wrap break-words outline-none',\n inputVariants({\n variant,\n validationStyleEnabled: validationStyleEnabled\n ? 'enabled'\n : 'disabled',\n }),\n autoSize && 'overflow-y-auto',\n className\n )}\n >\n {lines.map((text, i) => (\n <Line\n key={i}\n index={i}\n text={text}\n isLast={i === lines.length - 1}\n ghostText={hasGhost && ghostLine === i ? ghostText : undefined}\n />\n ))}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;AAgCA,MAAM,mBAAmB;AAEzB,MAAM,wBAAwB,cAAsC;CAClE,MAAM,UAAU,UAAU,iBAAiB,cAAc;AACzD,KAAI,QAAQ,WAAW,EACrB,SAAQ,UAAU,eAAe,IAAI,MAAM,iBAAiB,CAAC,KAAK,GAAG;AAGvE,QAAO,MAAM,KAAK,QAAQ,CACvB,KAAK,OAAO;EAEX,MAAM,MADW,GAAG,cAAc,kBAAkB,EAC9B,eAAe,GAAG,eAAe;AACvD,SAAO,QAAQ,mBACX,KACA,IAAI,MAAM,iBAAiB,CAAC,KAAK,GAAG;GACxC,CACD,KAAK,KAAK;;AAGf,MAAM,cAAc,SAA2B;CAC7C,MAAM,QAAQ,KAAK,MAAM,KAAK;AAC9B,QAAO,MAAM,WAAW,IAAI,CAAC,GAAG,GAAG;;AASrC,MAAM,gCAA0D;AAC9D,KAAI,OAAO,SAAS,eAAe,EAAE,eAAe,MAAO,QAAO;CAClE,MAAM,gBACJ,KAMA;AACF,QAAO,IAAI,cAAc,QAAW,EAAE,aAAa,YAAY,CAAC;;AAGlE,MAAM,oBAAoB,yBAAyB;;;;;AAMnD,MAAM,wBAAwB,MAAc,WAA2B;AACrE,KAAI,UAAU,EAAG,QAAO;AAExB,KAAI,mBAAmB;EACrB,MAAM,WAAW,CAAC,GAAG,kBAAkB,QAAQ,KAAK,MAAM,GAAG,OAAO,CAAC,CAAC;EACtE,MAAM,OAAO,SAAS,SAAS,SAAS;AACxC,SAAO,OAAO,SAAS,KAAK,QAAQ,SAAS,SAAS;;CAIxD,MAAM,OAAO,KAAK,WAAW,SAAS,EAAE;AACxC,KAAI,QAAQ,SAAU,QAAQ,SAAU,UAAU,EAChD,QAAO,SAAS;AAElB,QAAO,SAAS;;;;;AAMlB,MAAM,wBAAwB,MAAc,WAA2B;AACrE,KAAI,UAAU,KAAK,OAAQ,QAAO,KAAK;AAEvC,KAAI,mBAAmB;EAErB,MAAM,QADW,CAAC,GAAG,kBAAkB,QAAQ,KAAK,MAAM,OAAO,CAAC,CAAC,CAC5C;AACvB,SAAO,QAAQ,SAAS,MAAM,QAAQ,SAAS,SAAS;;CAI1D,MAAM,OAAO,KAAK,WAAW,OAAO;AACpC,KAAI,QAAQ,SAAU,QAAQ,SAAU,SAAS,IAAI,KAAK,OACxD,QAAO,SAAS;AAElB,QAAO,SAAS;;;;;AAMlB,MAAM,oBAAoB,MAAc,WAA2B;AACjE,KAAI,UAAU,EAAG,QAAO;CACxB,IAAI,IAAI,SAAS;AAEjB,QAAO,IAAI,KAAK,KAAK,KAAK,KAAK,IAAI,GAAG,CAAE;AAExC,QAAO,IAAI,KAAK,KAAK,KAAK,KAAK,IAAI,GAAG,CAAE;AACxC,QAAO;;;;;AAMT,MAAM,oBAAoB,MAAc,WAA2B;AACjE,KAAI,UAAU,KAAK,OAAQ,QAAO,KAAK;CACvC,IAAI,IAAI;AAER,QAAO,IAAI,KAAK,UAAU,KAAK,KAAK,KAAK,GAAG,CAAE;AAE9C,QAAO,IAAI,KAAK,UAAU,KAAK,KAAK,KAAK,GAAG,CAAE;AAC9C,QAAO;;;;;AAMT,MAAM,aAAa,MAAc,WAA2B;AAG1D,QAFe,KAAK,MAAM,GAAG,OAAO,CACT,YAAY,KAAK,GACvB;;;;;AAMvB,MAAM,WAAW,MAAc,WAA2B;CACxD,MAAM,cAAc,KAAK,QAAQ,MAAM,OAAO;AAC9C,QAAO,gBAAgB,KAAK,KAAK,SAAS;;AAG5C,MAAa,sBAAsB,EACjC,OACA,cACA,UACA,WAAW,YACoB;CAC/B,MAAM,eAAe,SAAS,gBAAgB;CAC9C,MAAM,CAAC,OAAO,YAAY,eAAyB,WAAW,aAAa,CAAC;CAC5E,MAAM,eAAe,OAA8B,KAAK;CACxD,MAAM,kBAAkB,OAA6B,KAAK;CAC1D,MAAM,eAAe,UAAU;CAG/B,MAAM,WAAW,OAAO,MAAM;AAC9B,UAAS,UAAU;AAEnB,iBAAgB;AACd,MAAI,gBAAgB,UAAU,OAC5B,UAAS,WAAW,MAAM,CAAC;IAE5B,CAAC,OAAO,aAAa,CAAC;CAEzB,MAAM,gBAAgB,SAAS,QAAQ,KAAK,KAAK;CAEjD,MAAM,yBAA+C;EACnD,MAAM,MAAM,OAAO,cAAc;AACjC,MAAI,CAAC,KAAK,cAAc,CAAC,aAAa,QAAS,QAAO;EAEtD,MAAM,QAAQ,IAAI,WAAW,EAAE;EAC/B,MAAM,UAAU,aAAa,QAAQ,iBAAiB,cAAc;AAEpE,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,IAClC,KAAI,QAAQ,GAAG,SAAS,MAAM,eAAe,CAC3C,QAAO;GAAE,MAAM;GAAG,QAAQ,MAAM;GAAa;AAGjD,SAAO;;CAGT,MAAM,4BAIM;EACV,MAAM,MAAM,OAAO,cAAc;AACjC,MAAI,CAAC,KAAK,cAAc,CAAC,aAAa,QAAS,QAAO;EAEtD,MAAM,QAAQ,IAAI,WAAW,EAAE;EAC/B,MAAM,UAAU,aAAa,QAAQ,iBAAiB,cAAc;EACpE,MAAM,eAAe,SAAS;EAE9B,MAAM,cAAc,MAAY,eAA+B;AAC7D,QAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,IAClC,KAAI,QAAQ,GAAG,SAAS,KAAK,EAAE;IAC7B,IAAI,OAAO;AACX,SAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IACrB,SAAQ,aAAa,GAAG,SAAS;AAEnC,WAAO,OAAO,KAAK,IAAI,YAAY,aAAa,IAAI,UAAU,EAAE;;AAIpE,OAAI,SAAS,aAAa,SAAS;AACjC,QAAI,eAAe,EAAG,QAAO;AAC7B,WAAO,aAAa,KAAK,KAAK,CAAC;;AAEjC,UAAO;;EAGT,MAAM,QAAQ,WAAW,MAAM,gBAAgB,MAAM,YAAY;EACjE,MAAM,MAAM,WAAW,MAAM,cAAc,MAAM,UAAU;AAE3D,SAAO;GACL,OAAO,KAAK,IAAI,OAAO,IAAI;GAC3B,KAAK,KAAK,IAAI,OAAO,IAAI;GACzB,cAAc,CAAC,MAAM;GACtB;;CAGH,MAAM,oBAAoB,QAAuB;AAC/C,MAAI,CAAC,aAAa,QAAS;EAG3B,MAAM,SADU,aAAa,QAAQ,iBAAiB,cAAc,CAC7C,IAAI;AAC3B,MAAI,CAAC,OAAQ;EAEb,MAAM,WAAW,OAAO,cAAc,kBAAkB;EACxD,MAAM,OACJ,UAAU,cAAc,YAAY,OAAO,cAAc;EAE3D,MAAM,MAAM,OAAO,cAAc;AACjC,MAAI,CAAC,IAAK;EAEV,MAAM,QAAQ,SAAS,aAAa;EACpC,MAAM,SAAS,KAAK,IAAI,IAAI,QAAQ,KAAK,aAAa,UAAU,EAAE;AAElE,MAAI;AACF,SAAM,SAAS,MAAM,OAAO;AAC5B,SAAM,SAAS,KAAK;AACpB,OAAI,iBAAiB;AACrB,OAAI,SAAS,MAAM;UACb;AACN,SAAM,mBAAmB,KAAK;AAC9B,SAAM,SAAS,MAAM;AACrB,OAAI,iBAAiB;AACrB,OAAI,SAAS,MAAM;;;AAIvB,iBAAgB;AACd,MAAI,gBAAgB,WAAW,aAAa,SAAS;AACnD,oBAAiB,gBAAgB,QAAQ;AACzC,mBAAgB,UAAU;;GAE5B;CAEF,MAAM,uBAAuB,QAA+B;EAC1D,MAAM,eAAe,SAAS;EAC9B,IAAI,SAAS;AACb,OAAK,IAAI,IAAI,GAAG,IAAI,IAAI,MAAM,IAC5B,WAAU,aAAa,GAAG,SAAS;AAErC,SAAO,SAAS,IAAI;;CAGtB,MAAM,uBACJ,MACA,gBACkB;EAClB,IAAI,MAAM;AACV,OAAK,IAAI,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AAC3C,OAAI,OAAO,YAAY,GAAG,OACxB,QAAO;IAAE,MAAM;IAAG,QAAQ;IAAK;AAEjC,UAAO,YAAY,GAAG,SAAS;;AAEjC,SAAO;GACL,MAAM,YAAY,SAAS;GAC3B,QAAQ,YAAY,YAAY,SAAS,IAAI,UAAU;GACxD;;CAGH,MAAM,wBAAgC;EACpC,MAAM,MAAM,kBAAkB;AAC9B,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,oBAAoB,IAAI;;;;;CAMjC,MAAM,mBAAmB,SAAiB,gBAAwB;EAChE,MAAM,WAAW,WAAW,QAAQ;AACpC,kBAAgB,UAAU,oBAAoB,aAAa,SAAS;AACpE,WAAS,SAAS;AAClB,aAAW,QAAQ;;CAGrB,MAAM,oBAAoB;AACxB,MAAI,gBAAgB,YAAY,KAAM;AACtC,MAAI,YAAY,CAAC,aAAa,QAAS;EAEvC,MAAM,WAAW,kBAAkB;EACnC,MAAM,UAAU,qBAAqB,aAAa,QAAQ;EAC1D,MAAM,WAAW,WAAW,QAAQ;AAEpC,kBAAgB,UAAU;AAC1B,WAAS,SAAS;AAClB,aAAW,QAAQ;;CAGrB,MAAM,iBAAiB,MAAqC;AAC1D,MAAI,UAAU;AACZ,KAAE,gBAAgB;AAClB;;AAIF,MAAI,EAAE,YAAY,YAAa;AAG/B,OAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,KAAK;AAC7C,KAAE,gBAAgB;AAClB;;EAGF,MAAM,UAAU,qBAAqB;AACrC,MAAI,CAAC,QAAS;EAEd,MAAM,cAAc,SAAS,QAAQ,KAAK,KAAK;AAE/C,MAAI,EAAE,QAAQ,SAAS;AACrB,KAAE,gBAAgB;AAKlB,mBAHE,YAAY,MAAM,GAAG,QAAQ,MAAM,GACnC,OACA,YAAY,MAAM,QAAQ,IAAI,EACP,QAAQ,QAAQ,EAAE;AAC3C;;AAGF,MAAI,EAAE,QAAQ,aAAa;AACzB,KAAE,gBAAgB;AAElB,OAAI,QAAQ,aAGV,iBADE,YAAY,MAAM,GAAG,QAAQ,MAAM,GAAG,YAAY,MAAM,QAAQ,IAAI,EAC7C,QAAQ,MAAM;QAClC;AACL,QAAI,QAAQ,UAAU,EAAG;IAEzB,IAAI;AACJ,QAAI,EAAE,QAEJ,cAAa,UAAU,aAAa,QAAQ,MAAM;aACzC,EAAE,OAEX,cAAa,iBAAiB,aAAa,QAAQ,MAAM;QAGzD,cAAa,qBAAqB,aAAa,QAAQ,MAAM;AAK/D,oBADE,YAAY,MAAM,GAAG,WAAW,GAAG,YAAY,MAAM,QAAQ,MAAM,EAC5C,WAAW;;AAEtC;;AAGF,MAAI,EAAE,QAAQ,UAAU;AACtB,KAAE,gBAAgB;AAElB,OAAI,QAAQ,aAGV,iBADE,YAAY,MAAM,GAAG,QAAQ,MAAM,GAAG,YAAY,MAAM,QAAQ,IAAI,EAC7C,QAAQ,MAAM;QAClC;AACL,QAAI,QAAQ,SAAS,YAAY,OAAQ;IAEzC,IAAI;AACJ,QAAI,EAAE,QAEJ,YAAW,QAAQ,aAAa,QAAQ,MAAM;aACrC,EAAE,OAEX,YAAW,iBAAiB,aAAa,QAAQ,MAAM;QAGvD,YAAW,qBAAqB,aAAa,QAAQ,MAAM;AAK7D,oBADE,YAAY,MAAM,GAAG,QAAQ,MAAM,GAAG,YAAY,MAAM,SAAS,EAC1C,QAAQ,MAAM;;AAEzC;;;CAIJ,MAAM,aAAa,MAAsC;AACvD,MAAI,UAAU;AACZ,KAAE,gBAAgB;AAClB;;AAGF,IAAE,gBAAgB;EAClB,MAAM,UAAU,qBAAqB;AACrC,MAAI,CAAC,SAAS,aAAc;EAE5B,MAAM,cAAc,SAAS,QAAQ,KAAK,KAAK;EAC/C,MAAM,eAAe,YAAY,MAAM,QAAQ,OAAO,QAAQ,IAAI;AAGlE,IAAE,cAAc,QAAQ,cAAc,aAAa;AAKnD,kBADE,YAAY,MAAM,GAAG,QAAQ,MAAM,GAAG,YAAY,MAAM,QAAQ,IAAI,EAC7C,QAAQ,MAAM;;CAGzC,MAAM,eAAe,MAAsC;AACzD,MAAI,UAAU;AACZ,KAAE,gBAAgB;AAClB;;AAGF,IAAE,gBAAgB;EAClB,MAAM,aAAa,EAAE,cAAc,QAAQ,aAAa;AACxD,MAAI,CAAC,WAAY;EAEjB,MAAM,UAAU,qBAAqB;AACrC,MAAI,CAAC,QAAS;EAEd,MAAM,cAAc,SAAS,QAAQ,KAAK,KAAK;AAK/C,kBAHE,YAAY,MAAM,GAAG,QAAQ,MAAM,GACnC,aACA,YAAY,MAAM,QAAQ,IAAI,EACP,QAAQ,QAAQ,WAAW,OAAO;;CAG7D,MAAM,qBAAqB,MAAkC;AAC3D,MAAI,SAAU;EAEd,MAAM,aAAa,EAAE;AAGrB,MAAI,WAAW,YAAa;EAE5B,MAAM,YAAY,WAAW;AAG7B,MAAI,cAAc,qBAAqB,cAAc,kBACnD;AAIF,MACE,cAAc,2BACd,cAAc,wBACd;AACA,KAAE,gBAAgB;GAClB,MAAM,UAAU,qBAAqB;AACrC,OAAI,CAAC,QAAS;GAEd,MAAM,cAAc,SAAS,QAAQ,KAAK,KAAK;AAE/C,OAAI,QAAQ,aAGV,iBADE,YAAY,MAAM,GAAG,QAAQ,MAAM,GAAG,YAAY,MAAM,QAAQ,IAAI,EAC7C,QAAQ,MAAM;YAC9B,cAAc,yBAAyB;AAChD,QAAI,QAAQ,UAAU,EAAG;IACzB,MAAM,aAAa,qBAAqB,aAAa,QAAQ,MAAM;AAGnE,oBADE,YAAY,MAAM,GAAG,WAAW,GAAG,YAAY,MAAM,QAAQ,MAAM,EAC5C,WAAW;UAC/B;AACL,QAAI,QAAQ,SAAS,YAAY,OAAQ;IACzC,MAAM,WAAW,qBAAqB,aAAa,QAAQ,MAAM;AAGjE,oBADE,YAAY,MAAM,GAAG,QAAQ,MAAM,GAAG,YAAY,MAAM,SAAS,EAC1C,QAAQ,MAAM;;AAEzC;;AAIF,MAAI,cAAc,yBAAyB;AACzC,KAAE,gBAAgB;GAClB,MAAM,UAAU,qBAAqB;AACrC,OAAI,CAAC,QAAS;GAEd,MAAM,cAAc,SAAS,QAAQ,KAAK,KAAK;GAC/C,MAAM,cACJ,WAAW,QAAQ,WAAW,cAAc,QAAQ,aAAa,IAAI;AAKvE,mBAHE,YAAY,MAAM,GAAG,QAAQ,MAAM,GACnC,cACA,YAAY,MAAM,QAAQ,IAAI,EACP,QAAQ,QAAQ,YAAY,OAAO;AAC5D;;AAGF,MAAI,cAAc,gBAAgB,WAAW,MAAM;AACjD,KAAE,gBAAgB;GAElB,MAAM,UAAU,qBAAqB;AACrC,OAAI,CAAC,QAAS;GAEd,MAAM,cAAc,SAAS,QAAQ,KAAK,KAAK;GAC/C,MAAM,WAAW,WAAW;AAK5B,mBAHE,YAAY,MAAM,GAAG,QAAQ,MAAM,GACnC,WACA,YAAY,MAAM,QAAQ,IAAI,EACP,QAAQ,QAAQ,SAAS,OAAO;;;CAI7D,MAAM,cAAc,MAAiC;AAEnD,IAAE,gBAAgB;;CAGpB,MAAM,kBAAkB,MAAiC;AACvD,IAAE,gBAAgB;;AAGpB,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;;AAUH,MAAM,QAAuB,EAAE,OAAO,MAAM,QAAQ,gBAClD,qBAAC,QAAD;CAAM,aAAW;CAAO,WAAU;WAAlC;EACE,oBAAC,QAAD;GAAM;aAAe,QAAQ;GAAgB;EAC5C,aACC,oBAAC,QAAD;GACE;GACA,WAAU;GACV,eAAY;aAEX;GACI;EAER,CAAC,UAAU,oBAAC,MAAD,EAAM;EACb;;AAmCT,MAAM,cAAc;AACpB,MAAM,eAAe;AAErB,MAAa,2BAA6D,EACxE,OACA,cACA,UACA,aACA,WAAW,OACX,UAAU,GACV,UAAU,KACV,WAAW,MACX,yBAAyB,OACzB,SACA,WACA,WACA,aACA,SACA,WACA,MAAM,QACN,KACA,GAAG,WACC;CACJ,MAAM,EACJ,OACA,cACA,SACA,aACA,mBACA,eACA,WACA,aACA,YACA,gBACA,iBACA,kBACA,wBACE,mBAAmB;EAAE;EAAO;EAAc;EAAU;EAAU,CAAC;CAEnE,MAAM,QAAQ,OAA8B,KAAK;CAEjD,MAAM,UAAU,OAA8B;AAC5C,QAAM,UAAU;AAChB,EAAC,aAAyD,UAAU;;AAGtE,qBAAoB,YAAY;EAC9B,oBAAoB,MAAM;EAC1B;EACA,aAAa,MAAM,SAAS,OAAO;EACnC;EACA,oBAAoB,WAAmB;AACrC,oBAAiB,oBAAoB,QAAQ,MAAM,CAAC;;EAEvD,EAAE;AAEH,iBAAgB;AACd,MAAI,CAAC,YAAY,CAAC,MAAM,QAAS;EAEjC,MAAM,KAAK,MAAM;EACjB,MAAM,MAAM,cAAc,UAAU;EACpC,MAAM,MAAM,cAAc,UAAU;AAEpC,KAAG,MAAM,SAAS;EAClB,MAAM,KAAK,GAAG;AACd,KAAG,MAAM,SAAS,GAAG,KAAK,IAAI,KAAK,IAAI,IAAI,IAAI,EAAE,IAAI,CAAC;AACtD,KAAG,MAAM,YAAY,KAAK,MAAM,SAAS;IACxC;EAAC;EAAO;EAAU;EAAS;EAAQ,CAAC;CAEvC,MAAM,UAAU,MAAM,WAAW,KAAK,MAAM,OAAO;CACnD,MAAM,WACJ,aAAa,cAAc,UAAa,gBAAgB;AAE1D,QACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACG,WAAW,eACV,oBAAC,OAAD;GACE,WAAU;GACV,eAAY;aAEX;GACG,GAGR,oBAAC,OAAD;GACE,GAAI;GACJ,KAAK;GACL,MAAK;GACL,kBAAe;GACf,oBAAkB;GAClB,iBAAe;GACf,qBAAmB,WAAW,WAAW;GACzC,UAAU,WAAW,KAAK;GAC1B,iBAAiB,CAAC;GAClB;GACK;GACL,SAAS;GACT,eAAe;GACf,WAAW;GACX,OAAO;GACP,SAAS;GACT,QAAQ;GACR,YAAY;GACH;GACT,WAAW,GACT,4DACA,cAAc;IACZ;IACA,wBAAwB,yBACpB,YACA;IACL,CAAC,EACF,YAAY,mBACZ,UACD;aAEA,MAAM,KAAK,MAAM,MAChB,oBAAC,MAAD;IAEE,OAAO;IACD;IACN,QAAQ,MAAM,MAAM,SAAS;IAC7B,WAAW,YAAY,cAAc,IAAI,YAAY;IACrD,EALK,EAKL,CACF;GACE,EACF"}
|
|
1
|
+
{"version":3,"file":"ContentEditableTextArea.mjs","names":[],"sources":["../../../../src/components/TextArea/ContentEditableTextArea.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport type { VariantProps } from 'class-variance-authority';\nimport {\n type ClipboardEvent,\n type DragEvent,\n type FC,\n type HTMLAttributes,\n type InputEvent,\n type KeyboardEvent,\n type MutableRefObject,\n type Ref,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react';\nimport { type InputVariant, inputVariants } from '../Input';\n\ntype CaretPosition = {\n line: number;\n offset: number;\n};\n\ntype UseContentEditableOptions = {\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n disabled?: boolean;\n};\n\nconst ZERO_WIDTH_SPACE = '\\u200B';\n\nconst getTextFromContainer = (container: HTMLDivElement): string => {\n const lineEls = container.querySelectorAll('[data-line]');\n if (lineEls.length === 0) {\n return (container.textContent ?? '').split(ZERO_WIDTH_SPACE).join('');\n }\n\n return Array.from(lineEls)\n .map((el) => {\n const editable = el.querySelector('[data-editable]');\n const raw = editable?.textContent ?? el.textContent ?? '';\n return raw === ZERO_WIDTH_SPACE\n ? ''\n : raw.split(ZERO_WIDTH_SPACE).join('');\n })\n .join('\\n');\n};\n\nconst splitLines = (text: string): string[] => {\n const lines = text.split('\\n');\n return lines.length === 0 ? [''] : lines;\n};\n\n// Cached Intl.Segmenter for grapheme-aware deletion (emoji, CJK, etc.)\n// Intl.Segmenter is ES2022+ but we feature-detect at runtime.\ntype GraphemeSegmenter = {\n segment: (input: string) => Iterable<{ segment: string }>;\n};\n\nconst createGraphemeSegmenter = (): GraphemeSegmenter | null => {\n if (typeof Intl === 'undefined' || !('Segmenter' in Intl)) return null;\n const SegmenterCtor = (\n Intl as unknown as Record<\n string,\n new (\n ...args: unknown[]\n ) => GraphemeSegmenter\n >\n ).Segmenter;\n return new SegmenterCtor(undefined, { granularity: 'grapheme' });\n};\n\nconst graphemeSegmenter = createGraphemeSegmenter();\n\n/**\n * Find the previous grapheme cluster boundary for safe deletion.\n * Falls back to code-point-aware deletion if Intl.Segmenter is unavailable.\n */\nconst prevGraphemeBoundary = (text: string, offset: number): number => {\n if (offset <= 0) return 0;\n\n if (graphemeSegmenter) {\n const segments = [...graphemeSegmenter.segment(text.slice(0, offset))];\n const last = segments[segments.length - 1];\n return last ? offset - last.segment.length : offset - 1;\n }\n\n // Fallback: handle surrogate pairs\n const code = text.charCodeAt(offset - 1);\n if (code >= 0xdc00 && code <= 0xdfff && offset >= 2) {\n return offset - 2;\n }\n return offset - 1;\n};\n\n/**\n * Find the next grapheme cluster boundary for safe forward deletion.\n */\nconst nextGraphemeBoundary = (text: string, offset: number): number => {\n if (offset >= text.length) return text.length;\n\n if (graphemeSegmenter) {\n const segments = [...graphemeSegmenter.segment(text.slice(offset))];\n const first = segments[0];\n return first ? offset + first.segment.length : offset + 1;\n }\n\n // Fallback: handle surrogate pairs\n const code = text.charCodeAt(offset);\n if (code >= 0xd800 && code <= 0xdbff && offset + 1 < text.length) {\n return offset + 2;\n }\n return offset + 1;\n};\n\n/**\n * Find the previous word boundary for Option+Backspace.\n */\nconst prevWordBoundary = (text: string, offset: number): number => {\n if (offset <= 0) return 0;\n let i = offset - 1;\n // Skip whitespace\n while (i > 0 && /\\s/.test(text[i - 1])) i--;\n // Skip word characters\n while (i > 0 && /\\S/.test(text[i - 1])) i--;\n return i;\n};\n\n/**\n * Find the next word boundary for Option+Delete.\n */\nconst nextWordBoundary = (text: string, offset: number): number => {\n if (offset >= text.length) return text.length;\n let i = offset;\n // Skip word characters\n while (i < text.length && /\\S/.test(text[i])) i++;\n // Skip whitespace\n while (i < text.length && /\\s/.test(text[i])) i++;\n return i;\n};\n\n/**\n * Find the start of the current line (for Cmd+Backspace).\n */\nconst lineStart = (text: string, offset: number): number => {\n const before = text.slice(0, offset);\n const lastNewline = before.lastIndexOf('\\n');\n return lastNewline + 1;\n};\n\n/**\n * Find the end of the current line (for Cmd+Delete).\n */\nconst lineEnd = (text: string, offset: number): number => {\n const nextNewline = text.indexOf('\\n', offset);\n return nextNewline === -1 ? text.length : nextNewline;\n};\n\nexport const useContentEditable = ({\n value,\n defaultValue,\n onChange,\n disabled = false,\n}: UseContentEditableOptions) => {\n const initialValue = value ?? defaultValue ?? '';\n const [lines, setLines] = useState<string[]>(() => splitLines(initialValue));\n const containerRef = useRef<HTMLDivElement | null>(null);\n const pendingCaretRef = useRef<CaretPosition | null>(null);\n const isControlled = value !== undefined;\n\n // Keep a ref to the latest lines to avoid stale closures in rapid typing\n const linesRef = useRef(lines);\n linesRef.current = lines;\n\n useEffect(() => {\n if (isControlled && value !== undefined) {\n setLines(splitLines(value));\n }\n }, [value, isControlled]);\n\n const getText = () => linesRef.current.join('\\n');\n\n const getCaretPosition = (): CaretPosition | null => {\n const sel = window.getSelection();\n if (!sel?.rangeCount || !containerRef.current) return null;\n\n const range = sel.getRangeAt(0);\n const lineEls = containerRef.current.querySelectorAll('[data-line]');\n\n for (let i = 0; i < lineEls.length; i++) {\n if (lineEls[i].contains(range.startContainer)) {\n return { line: i, offset: range.startOffset };\n }\n }\n return null;\n };\n\n const getSelectionOffsets = (): {\n start: number;\n end: number;\n hasSelection: boolean;\n } | null => {\n const sel = window.getSelection();\n if (!sel?.rangeCount || !containerRef.current) return null;\n\n const range = sel.getRangeAt(0);\n const lineEls = containerRef.current.querySelectorAll('[data-line]');\n const currentLines = linesRef.current;\n\n const findOffset = (node: Node, nodeOffset: number): number => {\n for (let i = 0; i < lineEls.length; i++) {\n if (lineEls[i].contains(node)) {\n let flat = 0;\n for (let j = 0; j < i; j++) {\n flat += currentLines[j].length + 1;\n }\n return flat + Math.min(nodeOffset, currentLines[i]?.length ?? 0);\n }\n }\n // Selection is on the root container (e.g. select-all)\n if (node === containerRef.current) {\n if (nodeOffset === 0) return 0;\n return currentLines.join('\\n').length;\n }\n return 0;\n };\n\n const start = findOffset(range.startContainer, range.startOffset);\n const end = findOffset(range.endContainer, range.endOffset);\n\n return {\n start: Math.min(start, end),\n end: Math.max(start, end),\n hasSelection: !range.collapsed,\n };\n };\n\n const setCaretPosition = (pos: CaretPosition) => {\n if (!containerRef.current) return;\n\n const lineEls = containerRef.current.querySelectorAll('[data-line]');\n const lineEl = lineEls[pos.line];\n if (!lineEl) return;\n\n const editable = lineEl.querySelector('[data-editable]');\n const node =\n editable?.firstChild ?? editable ?? lineEl.firstChild ?? lineEl;\n\n const sel = window.getSelection();\n if (!sel) return;\n\n const range = document.createRange();\n const maxOff = Math.min(pos.offset, node.textContent?.length ?? 0);\n\n try {\n range.setStart(node, maxOff);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n } catch {\n range.selectNodeContents(node);\n range.collapse(false);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n };\n\n useEffect(() => {\n if (pendingCaretRef.current && containerRef.current) {\n setCaretPosition(pendingCaretRef.current);\n pendingCaretRef.current = null;\n }\n });\n\n const flatOffsetFromCaret = (pos: CaretPosition): number => {\n const currentLines = linesRef.current;\n let offset = 0;\n for (let i = 0; i < pos.line; i++) {\n offset += currentLines[i].length + 1;\n }\n return offset + pos.offset;\n };\n\n const caretFromFlatOffset = (\n flat: number,\n targetLines: string[]\n ): CaretPosition => {\n let rem = flat;\n for (let i = 0; i < targetLines.length; i++) {\n if (rem <= targetLines[i].length) {\n return { line: i, offset: rem };\n }\n rem -= targetLines[i].length + 1;\n }\n return {\n line: targetLines.length - 1,\n offset: targetLines[targetLines.length - 1]?.length ?? 0,\n };\n };\n\n const getCursorOffset = (): number => {\n const pos = getCaretPosition();\n if (!pos) return 0;\n return flatOffsetFromCaret(pos);\n };\n\n /**\n * Applies a text mutation: computes new lines, sets pending caret, updates state.\n */\n const applyTextChange = (newText: string, caretOffset: number) => {\n const newLines = splitLines(newText);\n pendingCaretRef.current = caretFromFlatOffset(caretOffset, newLines);\n setLines(newLines);\n onChange?.(newText);\n };\n\n const handleInput = () => {\n if (pendingCaretRef.current !== null) return;\n if (disabled || !containerRef.current) return;\n\n const caretPos = getCaretPosition();\n const newText = getTextFromContainer(containerRef.current);\n const newLines = splitLines(newText);\n\n pendingCaretRef.current = caretPos;\n setLines(newLines);\n onChange?.(newText);\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (disabled) {\n e.preventDefault();\n return;\n }\n\n // Don't intercept during IME composition (CJK input)\n if (e.nativeEvent.isComposing) return;\n\n // Block undo/redo - browser would mutate DOM out of sync with React\n if ((e.metaKey || e.ctrlKey) && e.key === 'z') {\n e.preventDefault();\n return;\n }\n\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n\n if (e.key === 'Enter') {\n e.preventDefault();\n const newText =\n currentText.slice(0, selInfo.start) +\n '\\n' +\n currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start + 1);\n return;\n }\n\n if (e.key === 'Backspace') {\n e.preventDefault();\n\n if (selInfo.hasSelection) {\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start);\n } else {\n if (selInfo.start === 0) return;\n\n let deleteFrom: number;\n if (e.metaKey) {\n // Cmd+Backspace: delete to start of line\n deleteFrom = lineStart(currentText, selInfo.start);\n } else if (e.altKey) {\n // Option+Backspace: delete previous word\n deleteFrom = prevWordBoundary(currentText, selInfo.start);\n } else {\n // Regular backspace: delete one grapheme\n deleteFrom = prevGraphemeBoundary(currentText, selInfo.start);\n }\n\n const newText =\n currentText.slice(0, deleteFrom) + currentText.slice(selInfo.start);\n applyTextChange(newText, deleteFrom);\n }\n return;\n }\n\n if (e.key === 'Delete') {\n e.preventDefault();\n\n if (selInfo.hasSelection) {\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start);\n } else {\n if (selInfo.start >= currentText.length) return;\n\n let deleteTo: number;\n if (e.metaKey) {\n // Cmd+Delete: delete to end of line\n deleteTo = lineEnd(currentText, selInfo.start);\n } else if (e.altKey) {\n // Option+Delete: delete next word\n deleteTo = nextWordBoundary(currentText, selInfo.start);\n } else {\n // Regular delete: delete one grapheme\n deleteTo = nextGraphemeBoundary(currentText, selInfo.start);\n }\n\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(deleteTo);\n applyTextChange(newText, selInfo.start);\n }\n return;\n }\n };\n\n const handleCut = (e: ClipboardEvent<HTMLDivElement>) => {\n if (disabled) {\n e.preventDefault();\n return;\n }\n\n e.preventDefault();\n const selInfo = getSelectionOffsets();\n if (!selInfo?.hasSelection) return;\n\n const currentText = linesRef.current.join('\\n');\n const selectedText = currentText.slice(selInfo.start, selInfo.end);\n\n // Write selected text to clipboard\n e.clipboardData.setData('text/plain', selectedText);\n\n // Delete the selected text\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start);\n };\n\n const handlePaste = (e: ClipboardEvent<HTMLDivElement>) => {\n if (disabled) {\n e.preventDefault();\n return;\n }\n\n e.preventDefault();\n const pastedText = e.clipboardData.getData('text/plain');\n if (!pastedText) return;\n\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n const newText =\n currentText.slice(0, selInfo.start) +\n pastedText +\n currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start + pastedText.length);\n };\n\n const handleBeforeInput = (e: InputEvent<HTMLDivElement>) => {\n if (disabled) return;\n\n const inputEvent = e.nativeEvent as InputEvent;\n\n // Don't intercept during IME composition (CJK input)\n if (inputEvent.isComposing) return;\n\n const inputType = inputEvent.inputType;\n\n // Skip types handled by handleKeyDown (when keydown fires)\n if (inputType === 'insertParagraph' || inputType === 'insertLineBreak') {\n return;\n }\n\n // Handle deletions as fallback for mobile keyboards that don't fire keydown\n if (\n inputType === 'deleteContentBackward' ||\n inputType === 'deleteContentForward'\n ) {\n e.preventDefault();\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n\n if (selInfo.hasSelection) {\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start);\n } else if (inputType === 'deleteContentBackward') {\n if (selInfo.start === 0) return;\n const deleteFrom = prevGraphemeBoundary(currentText, selInfo.start);\n const newText =\n currentText.slice(0, deleteFrom) + currentText.slice(selInfo.start);\n applyTextChange(newText, deleteFrom);\n } else {\n if (selInfo.start >= currentText.length) return;\n const deleteTo = nextGraphemeBoundary(currentText, selInfo.start);\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(deleteTo);\n applyTextChange(newText, selInfo.start);\n }\n return;\n }\n\n // Handle spell-check replacements\n if (inputType === 'insertReplacementText') {\n e.preventDefault();\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n const replacement =\n inputEvent.data ?? inputEvent.dataTransfer?.getData('text/plain') ?? '';\n const newText =\n currentText.slice(0, selInfo.start) +\n replacement +\n currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start + replacement.length);\n return;\n }\n\n if (inputType === 'insertText' && inputEvent.data) {\n e.preventDefault();\n\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n const inserted = inputEvent.data;\n const newText =\n currentText.slice(0, selInfo.start) +\n inserted +\n currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start + inserted.length);\n }\n };\n\n const handleDrop = (e: DragEvent<HTMLDivElement>) => {\n // Block drag-and-drop to prevent uncontrolled DOM mutations\n e.preventDefault();\n };\n\n const handleDragOver = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n };\n\n return {\n lines,\n containerRef,\n getText,\n handleInput,\n handleBeforeInput,\n handleKeyDown,\n handleCut,\n handlePaste,\n handleDrop,\n handleDragOver,\n getCaretPosition,\n setCaretPosition,\n getCursorOffset,\n caretFromFlatOffset,\n };\n};\n\ntype LineProps = {\n index: number;\n text: string;\n isLast: boolean;\n ghostText?: string;\n};\n\nconst Line: FC<LineProps> = ({ index, text, isLast, ghostText }) => (\n <span data-line={index} className=\"block min-h-[1.5rem]\">\n <span data-editable>{text || '\\u200B'}</span>\n {ghostText && (\n <span\n data-ghost\n className=\"pointer-events-none select-none text-neutral\"\n aria-hidden=\"true\"\n >\n {ghostText}\n </span>\n )}\n {!isLast && <br />}\n </span>\n);\n\nexport type ContentEditableTextAreaHandle = {\n getContainer: () => HTMLDivElement | null;\n getText: () => string;\n focus: () => void;\n getCursorOffset: () => number;\n setCursorAtOffset: (offset: number) => void;\n};\n\nexport type ContentEditableTextAreaProps = Omit<\n HTMLAttributes<HTMLDivElement>,\n 'onChange' | 'defaultValue'\n> & {\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n placeholder?: string;\n disabled?: boolean;\n minRows?: number;\n maxRows?: number;\n autoSize?: boolean;\n validationStyleEnabled?: boolean;\n variant?: InputVariant | `${InputVariant}`;\n ghostText?: string;\n ghostLine?: number;\n ghostOffset?: number;\n ref?: Ref<ContentEditableTextAreaHandle>;\n dir?: 'ltr' | 'rtl' | 'auto';\n} & Omit<\n VariantProps<typeof inputVariants>,\n 'validationStyleEnabled' | 'variant'\n >;\n\nconst LINE_HEIGHT = 24;\nconst LINE_PADDING = 12;\n\nexport const ContentEditableTextArea: FC<ContentEditableTextAreaProps> = ({\n value,\n defaultValue,\n onChange,\n placeholder,\n disabled = false,\n minRows = 1,\n maxRows = 999,\n autoSize = true,\n validationStyleEnabled = false,\n variant,\n ghostText,\n ghostLine,\n ghostOffset,\n onClick,\n className,\n dir = 'auto',\n ref,\n ...rest\n}) => {\n const {\n lines,\n containerRef,\n getText,\n handleInput,\n handleBeforeInput,\n handleKeyDown,\n handleCut,\n handlePaste,\n handleDrop,\n handleDragOver,\n getCursorOffset,\n setCaretPosition,\n caretFromFlatOffset,\n } = useContentEditable({ value, defaultValue, onChange, disabled });\n\n const elRef = useRef<HTMLDivElement | null>(null);\n\n const setRef = (el: HTMLDivElement | null) => {\n elRef.current = el;\n (containerRef as MutableRefObject<HTMLDivElement | null>).current = el;\n };\n\n useImperativeHandle(ref, () => ({\n getContainer: () => elRef.current,\n getText,\n focus: () => elRef.current?.focus(),\n getCursorOffset,\n setCursorAtOffset: (offset: number) => {\n setCaretPosition(caretFromFlatOffset(offset, lines));\n },\n }));\n\n useEffect(() => {\n if (!autoSize || !elRef.current) return;\n\n const el = elRef.current;\n const max = LINE_HEIGHT * maxRows + LINE_PADDING;\n const min = LINE_HEIGHT * minRows + LINE_PADDING;\n\n el.style.height = 'auto';\n const sh = el.scrollHeight;\n el.style.height = `${Math.max(Math.min(sh, max), min)}px`;\n el.style.overflowY = sh > max ? 'auto' : 'hidden';\n }, [lines, autoSize, maxRows, minRows]);\n\n const isEmpty = lines.length === 1 && lines[0] === '';\n const hasGhost =\n ghostText && ghostLine !== undefined && ghostOffset !== undefined;\n\n return (\n <div className=\"relative w-full\">\n {isEmpty && placeholder && (\n <div\n className=\"pointer-events-none absolute inset-0 select-none px-2 py-3 text-base text-neutral-400 leading-[1.5rem] md:py-2 md:text-sm\"\n aria-hidden=\"true\"\n >\n {placeholder}\n </div>\n )}\n\n <div\n {...rest}\n ref={setRef}\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-placeholder={placeholder}\n aria-disabled={disabled}\n aria-autocomplete={hasGhost ? 'inline' : undefined}\n tabIndex={disabled ? -1 : 0}\n contentEditable={!disabled}\n suppressContentEditableWarning\n dir={dir}\n onInput={handleInput}\n onBeforeInput={handleBeforeInput}\n onKeyDown={handleKeyDown}\n onCut={handleCut}\n onPaste={handlePaste}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onClick={onClick}\n className={cn(\n 'resize-none whitespace-pre-wrap break-words outline-none',\n inputVariants({\n variant,\n validationStyleEnabled: validationStyleEnabled\n ? 'enabled'\n : 'disabled',\n }),\n autoSize && 'overflow-y-auto',\n className\n )}\n >\n {lines.map((text, i) => (\n <Line\n key={i}\n index={i}\n text={text}\n isLast={i === lines.length - 1}\n ghostText={hasGhost && ghostLine === i ? ghostText : undefined}\n />\n ))}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -31,9 +31,14 @@ const ToastViewport = ({ className, ...props }) => /* @__PURE__ */ jsx(ToastPrim
|
|
|
31
31
|
* ```
|
|
32
32
|
*/
|
|
33
33
|
const toastVariants = cva("group data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md p-4 pr-6 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[state=closed]:animate-out data-[state=open]:animate-in data-[swipe=end]:animate-out data-[swipe=move]:transition-none", {
|
|
34
|
-
variants: {
|
|
34
|
+
variants: {
|
|
35
|
+
/** Toast visual variants for different message types */
|
|
36
|
+
variant: {
|
|
37
|
+
/** Error state with red styling for failures and warnings */
|
|
35
38
|
error: "bg-error/40 text-text",
|
|
39
|
+
/** Success state with green styling for confirmations */
|
|
36
40
|
success: "bg-success/30 text-text",
|
|
41
|
+
/** Default neutral styling for general information */
|
|
37
42
|
default: "bg-card/80 text-text"
|
|
38
43
|
} },
|
|
39
44
|
defaultVariants: { variant: "default" }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.mjs","names":[],"sources":["../../../../src/components/Toaster/Toast.tsx"],"sourcesContent":["'use client';\n\nimport * as ToastPrimitives from '@radix-ui/react-toast';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { X } from 'lucide-react';\nimport type { ComponentProps, FC, ReactElement } from 'react';\n\nconst ToastProvider = ToastPrimitives;\n\nconst ToastViewport: FC<ComponentProps<typeof ToastPrimitives.Viewport>> = ({\n className,\n ...props\n}) => (\n <ToastPrimitives.Viewport\n className={cn(\n 'fixed top-0 z-100 flex max-h-screen w-full flex-col-reverse p-4 sm:top-auto sm:right-0 sm:bottom-0 sm:flex-col md:max-w-[420px]',\n className\n )}\n {...props}\n />\n);\n\n/**\n * Toast variant styles using class-variance-authority.\n *\n * Defines visual styles for different toast types with semantic colors,\n * animations, and responsive behavior.\n *\n * @example\n * ```tsx\n * // Error toast with red background\n * <Toast variant=\"error\">Error message</Toast>\n *\n * // Success toast with green background\n * <Toast variant=\"success\">Success message</Toast>\n *\n * // Default toast with neutral styling\n * <Toast variant=\"default\">Info message</Toast>\n * ```\n */\nconst toastVariants = cva(\n 'group data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md p-4 pr-6 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[state=closed]:animate-out data-[state=open]:animate-in data-[swipe=end]:animate-out data-[swipe=move]:transition-none',\n {\n variants: {\n /** Toast visual variants for different message types */\n variant: {\n /** Error state with red styling for failures and warnings */\n error: 'bg-error/40 text-text',\n /** Success state with green styling for confirmations */\n success: 'bg-success/30 text-text',\n /** Default neutral styling for general information */\n default: 'bg-card/80 text-text',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n }\n);\n\n/**\n * Toast Component\n *\n * A notification component that displays temporary messages to users using Radix UI primitives.\n * Supports different visual variants, animations, and user interactions including swipe-to-dismiss.\n *\n * ## Features\n * - **Visual Variants**: Error, success, and default styling themes\n * - **Animations**: Smooth slide-in/slide-out transitions with fade effects\n * - **Swipe Gestures**: Touch-friendly swipe-to-dismiss functionality\n * - **Accessibility**: Full screen reader support and keyboard navigation\n * - **Positioning**: Smart positioning with responsive viewport handling\n * - **Auto-dismiss**: Configurable automatic dismissal timing\n *\n * ## Technical Implementation\n * - Built on Radix UI Toast primitives for accessibility compliance\n * - Uses Framer Motion for smooth animations and gestures\n * - CVA (class-variance-authority) for consistent styling variants\n * - Backdrop blur effects for modern visual appeal\n * - CSS transforms for hardware-accelerated animations\n *\n * @example\n * ```tsx\n * // Basic toast with title and description\n * <Toast variant=\"default\">\n * <ToastTitle>Notification</ToastTitle>\n * <ToastDescription>Your action was completed successfully.</ToastDescription>\n * <ToastClose />\n * </Toast>\n *\n * // Error toast with action button\n * <Toast variant=\"error\">\n * <ToastTitle>Upload Failed</ToastTitle>\n * <ToastDescription>Could not upload file. Please try again.</ToastDescription>\n * <ToastAction altText=\"Retry upload\">Retry</ToastAction>\n * <ToastClose />\n * </Toast>\n * ```\n */\nconst Toast: FC<\n ComponentProps<typeof ToastPrimitives.Root> &\n VariantProps<typeof toastVariants>\n> = ({ className, variant, ...props }) => {\n return (\n <ToastPrimitives.Root\n className={cn(toastVariants({ variant }), className)}\n {...props}\n />\n );\n};\n\n/**\n * ToastAction Component\n *\n * An interactive button component for toast notifications that allows users to take\n * actions related to the notification message.\n *\n * ## Features\n * - **Accessibility**: Requires `altText` prop for screen readers\n * - **Visual States**: Hover, focus, and disabled state styling\n * - **Theme Integration**: Supports destructive and default themes\n * - **Keyboard Navigation**: Full keyboard accessibility support\n *\n * ## Usage Guidelines\n * - Use for actionable notifications (retry, undo, view details)\n * - Keep action text short and descriptive\n * - Provide meaningful `altText` for accessibility\n * - Limit to one primary action per toast\n *\n * @example\n * ```tsx\n * // Retry action for failed operations\n * <ToastAction altText=\"Retry the failed operation\">\n * Retry\n * </ToastAction>\n *\n * // Undo action for reversible operations\n * <ToastAction altText=\"Undo the last action\">\n * Undo\n * </ToastAction>\n *\n * // Navigation action\n * <ToastAction altText=\"View the uploaded file\">\n * View File\n * </ToastAction>\n * ```\n */\nconst ToastAction: FC<ComponentProps<typeof ToastPrimitives.Action>> = ({\n className,\n ...props\n}) => (\n <ToastPrimitives.Action\n className={cn(\n 'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 font-medium text-sm transition-colors hover:bg-text focus:outline-hidden focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:focus:ring-destructive group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground',\n className\n )}\n {...props}\n />\n);\n\nconst ToastClose: FC<ComponentProps<typeof ToastPrimitives.Close>> = ({\n className,\n ...props\n}) => (\n <ToastPrimitives.Close\n className={cn(\n 'absolute top-1 right-1 rounded-md p-1 text-text/50 opacity-0 transition-opacity hover:text-text/80 focus:opacity-100 focus:outline-hidden focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 group-[.destructive]:hover:text-red-50',\n className\n )}\n toast-close=\"\"\n {...props}\n >\n <X className=\"size-5\" />\n </ToastPrimitives.Close>\n);\n/**\n * ToastTitle Component\n *\n * The primary heading text for toast notifications. Provides semantic structure\n * and proper typography hierarchy within the toast.\n *\n * ## Styling Features\n * - Semi-bold font weight for emphasis\n * - Automatic text sizing adjustments for descriptions\n * - Proper spacing relationships with other toast elements\n *\n * @example\n * ```tsx\n * <ToastTitle>File Upload Complete</ToastTitle>\n * <ToastTitle>Error: Connection Failed</ToastTitle>\n * <ToastTitle>Settings Saved</ToastTitle>\n * ```\n */\nconst ToastTitle: FC<ComponentProps<typeof ToastPrimitives.Title>> = ({\n className,\n ...props\n}) => (\n <ToastPrimitives.Title\n className={cn('font-semibold text-sm [&+div]:text-xs', className)}\n {...props}\n />\n);\n\n/**\n * ToastDescription Component\n *\n * Supporting text that provides additional context or details for the toast notification.\n * Complements the ToastTitle with more detailed information.\n *\n * ## Styling Features\n * - Slightly reduced opacity for visual hierarchy\n * - Smaller text size than title\n * - Optimal line height for readability\n *\n * ## Content Guidelines\n * - Keep descriptions concise but informative\n * - Provide actionable information when possible\n * - Use plain language for better accessibility\n *\n * @example\n * ```tsx\n * <ToastDescription>\n * Your document has been uploaded successfully and is now available for sharing.\n * </ToastDescription>\n *\n * <ToastDescription>\n * Please check your internet connection and try again.\n * </ToastDescription>\n * ```\n */\nconst ToastDescription: FC<\n ComponentProps<typeof ToastPrimitives.Description>\n> = ({ className, ...props }) => (\n <ToastPrimitives.Description\n className={cn('text-sm opacity-90', className)}\n {...props}\n />\n);\n/**\n * Props type for Toast component including all Radix UI Toast.Root props\n * and variant styling options.\n */\ntype ToastProps = ComponentProps<typeof Toast>;\n\n/**\n * Type for ToastAction elements used in toast configurations.\n * Ensures type safety when passing action elements to toast functions.\n */\ntype ToastActionElement = ReactElement<typeof ToastAction>;\n\nexport {\n Toast,\n ToastAction,\n ToastClose,\n ToastDescription,\n ToastProvider,\n ToastTitle,\n ToastViewport,\n type ToastActionElement,\n type ToastProps,\n};\n"],"mappings":";;;;;;;;;AAQA,MAAM,gBAAgB;AAEtB,MAAM,iBAAsE,EAC1E,WACA,GAAG,YAEH,oBAAC,gBAAgB,UAAjB;CACE,WAAW,GACT,mIACA,UACD;CACD,GAAI;CACJ;;;;;;;;;;;;;;;;;;;AAqBJ,MAAM,gBAAgB,IACpB,koBACA;CACE,UAAU,EAER,SAAS;EAEP,OAAO;EAEP,SAAS;EAET,SAAS;EACV,EACF;CACD,iBAAiB,EACf,SAAS,WACV;CACF,CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCD,MAAM,SAGD,EAAE,WAAW,SAAS,GAAG,YAAY;AACxC,QACE,oBAAC,gBAAgB,MAAjB;EACE,WAAW,GAAG,cAAc,EAAE,SAAS,CAAC,EAAE,UAAU;EACpD,GAAI;EACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCN,MAAM,eAAkE,EACtE,WACA,GAAG,YAEH,oBAAC,gBAAgB,QAAjB;CACE,WAAW,GACT,wdACA,UACD;CACD,GAAI;CACJ;AAGJ,MAAM,cAAgE,EACpE,WACA,GAAG,YAEH,oBAAC,gBAAgB,OAAjB;CACE,WAAW,GACT,kVACA,UACD;CACD,eAAY;CACZ,GAAI;WAEJ,oBAAC,GAAD,EAAG,WAAU,UAAW;CACF;;;;;;;;;;;;;;;;;;;AAoB1B,MAAM,cAAgE,EACpE,WACA,GAAG,YAEH,oBAAC,gBAAgB,OAAjB;CACE,WAAW,GAAG,yCAAyC,UAAU;CACjE,GAAI;CACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BJ,MAAM,oBAED,EAAE,WAAW,GAAG,YACnB,oBAAC,gBAAgB,aAAjB;CACE,WAAW,GAAG,sBAAsB,UAAU;CAC9C,GAAI;CACJ"}
|
|
1
|
+
{"version":3,"file":"Toast.mjs","names":[],"sources":["../../../../src/components/Toaster/Toast.tsx"],"sourcesContent":["'use client';\n\nimport * as ToastPrimitives from '@radix-ui/react-toast';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { X } from 'lucide-react';\nimport type { ComponentProps, FC, ReactElement } from 'react';\n\nconst ToastProvider = ToastPrimitives;\n\nconst ToastViewport: FC<ComponentProps<typeof ToastPrimitives.Viewport>> = ({\n className,\n ...props\n}) => (\n <ToastPrimitives.Viewport\n className={cn(\n 'fixed top-0 z-100 flex max-h-screen w-full flex-col-reverse p-4 sm:top-auto sm:right-0 sm:bottom-0 sm:flex-col md:max-w-[420px]',\n className\n )}\n {...props}\n />\n);\n\n/**\n * Toast variant styles using class-variance-authority.\n *\n * Defines visual styles for different toast types with semantic colors,\n * animations, and responsive behavior.\n *\n * @example\n * ```tsx\n * // Error toast with red background\n * <Toast variant=\"error\">Error message</Toast>\n *\n * // Success toast with green background\n * <Toast variant=\"success\">Success message</Toast>\n *\n * // Default toast with neutral styling\n * <Toast variant=\"default\">Info message</Toast>\n * ```\n */\nconst toastVariants = cva(\n 'group data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md p-4 pr-6 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[state=closed]:animate-out data-[state=open]:animate-in data-[swipe=end]:animate-out data-[swipe=move]:transition-none',\n {\n variants: {\n /** Toast visual variants for different message types */\n variant: {\n /** Error state with red styling for failures and warnings */\n error: 'bg-error/40 text-text',\n /** Success state with green styling for confirmations */\n success: 'bg-success/30 text-text',\n /** Default neutral styling for general information */\n default: 'bg-card/80 text-text',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n }\n);\n\n/**\n * Toast Component\n *\n * A notification component that displays temporary messages to users using Radix UI primitives.\n * Supports different visual variants, animations, and user interactions including swipe-to-dismiss.\n *\n * ## Features\n * - **Visual Variants**: Error, success, and default styling themes\n * - **Animations**: Smooth slide-in/slide-out transitions with fade effects\n * - **Swipe Gestures**: Touch-friendly swipe-to-dismiss functionality\n * - **Accessibility**: Full screen reader support and keyboard navigation\n * - **Positioning**: Smart positioning with responsive viewport handling\n * - **Auto-dismiss**: Configurable automatic dismissal timing\n *\n * ## Technical Implementation\n * - Built on Radix UI Toast primitives for accessibility compliance\n * - Uses Framer Motion for smooth animations and gestures\n * - CVA (class-variance-authority) for consistent styling variants\n * - Backdrop blur effects for modern visual appeal\n * - CSS transforms for hardware-accelerated animations\n *\n * @example\n * ```tsx\n * // Basic toast with title and description\n * <Toast variant=\"default\">\n * <ToastTitle>Notification</ToastTitle>\n * <ToastDescription>Your action was completed successfully.</ToastDescription>\n * <ToastClose />\n * </Toast>\n *\n * // Error toast with action button\n * <Toast variant=\"error\">\n * <ToastTitle>Upload Failed</ToastTitle>\n * <ToastDescription>Could not upload file. Please try again.</ToastDescription>\n * <ToastAction altText=\"Retry upload\">Retry</ToastAction>\n * <ToastClose />\n * </Toast>\n * ```\n */\nconst Toast: FC<\n ComponentProps<typeof ToastPrimitives.Root> &\n VariantProps<typeof toastVariants>\n> = ({ className, variant, ...props }) => {\n return (\n <ToastPrimitives.Root\n className={cn(toastVariants({ variant }), className)}\n {...props}\n />\n );\n};\n\n/**\n * ToastAction Component\n *\n * An interactive button component for toast notifications that allows users to take\n * actions related to the notification message.\n *\n * ## Features\n * - **Accessibility**: Requires `altText` prop for screen readers\n * - **Visual States**: Hover, focus, and disabled state styling\n * - **Theme Integration**: Supports destructive and default themes\n * - **Keyboard Navigation**: Full keyboard accessibility support\n *\n * ## Usage Guidelines\n * - Use for actionable notifications (retry, undo, view details)\n * - Keep action text short and descriptive\n * - Provide meaningful `altText` for accessibility\n * - Limit to one primary action per toast\n *\n * @example\n * ```tsx\n * // Retry action for failed operations\n * <ToastAction altText=\"Retry the failed operation\">\n * Retry\n * </ToastAction>\n *\n * // Undo action for reversible operations\n * <ToastAction altText=\"Undo the last action\">\n * Undo\n * </ToastAction>\n *\n * // Navigation action\n * <ToastAction altText=\"View the uploaded file\">\n * View File\n * </ToastAction>\n * ```\n */\nconst ToastAction: FC<ComponentProps<typeof ToastPrimitives.Action>> = ({\n className,\n ...props\n}) => (\n <ToastPrimitives.Action\n className={cn(\n 'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 font-medium text-sm transition-colors hover:bg-text focus:outline-hidden focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:focus:ring-destructive group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground',\n className\n )}\n {...props}\n />\n);\n\nconst ToastClose: FC<ComponentProps<typeof ToastPrimitives.Close>> = ({\n className,\n ...props\n}) => (\n <ToastPrimitives.Close\n className={cn(\n 'absolute top-1 right-1 rounded-md p-1 text-text/50 opacity-0 transition-opacity hover:text-text/80 focus:opacity-100 focus:outline-hidden focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 group-[.destructive]:hover:text-red-50',\n className\n )}\n toast-close=\"\"\n {...props}\n >\n <X className=\"size-5\" />\n </ToastPrimitives.Close>\n);\n/**\n * ToastTitle Component\n *\n * The primary heading text for toast notifications. Provides semantic structure\n * and proper typography hierarchy within the toast.\n *\n * ## Styling Features\n * - Semi-bold font weight for emphasis\n * - Automatic text sizing adjustments for descriptions\n * - Proper spacing relationships with other toast elements\n *\n * @example\n * ```tsx\n * <ToastTitle>File Upload Complete</ToastTitle>\n * <ToastTitle>Error: Connection Failed</ToastTitle>\n * <ToastTitle>Settings Saved</ToastTitle>\n * ```\n */\nconst ToastTitle: FC<ComponentProps<typeof ToastPrimitives.Title>> = ({\n className,\n ...props\n}) => (\n <ToastPrimitives.Title\n className={cn('font-semibold text-sm [&+div]:text-xs', className)}\n {...props}\n />\n);\n\n/**\n * ToastDescription Component\n *\n * Supporting text that provides additional context or details for the toast notification.\n * Complements the ToastTitle with more detailed information.\n *\n * ## Styling Features\n * - Slightly reduced opacity for visual hierarchy\n * - Smaller text size than title\n * - Optimal line height for readability\n *\n * ## Content Guidelines\n * - Keep descriptions concise but informative\n * - Provide actionable information when possible\n * - Use plain language for better accessibility\n *\n * @example\n * ```tsx\n * <ToastDescription>\n * Your document has been uploaded successfully and is now available for sharing.\n * </ToastDescription>\n *\n * <ToastDescription>\n * Please check your internet connection and try again.\n * </ToastDescription>\n * ```\n */\nconst ToastDescription: FC<\n ComponentProps<typeof ToastPrimitives.Description>\n> = ({ className, ...props }) => (\n <ToastPrimitives.Description\n className={cn('text-sm opacity-90', className)}\n {...props}\n />\n);\n/**\n * Props type for Toast component including all Radix UI Toast.Root props\n * and variant styling options.\n */\ntype ToastProps = ComponentProps<typeof Toast>;\n\n/**\n * Type for ToastAction elements used in toast configurations.\n * Ensures type safety when passing action elements to toast functions.\n */\ntype ToastActionElement = ReactElement<typeof ToastAction>;\n\nexport {\n Toast,\n ToastAction,\n ToastClose,\n ToastDescription,\n ToastProvider,\n ToastTitle,\n ToastViewport,\n type ToastActionElement,\n type ToastProps,\n};\n"],"mappings":";;;;;;;;;AAQA,MAAM,gBAAgB;AAEtB,MAAM,iBAAsE,EAC1E,WACA,GAAG,YAEH,oBAAC,gBAAgB,UAAjB;CACE,WAAW,GACT,mIACA,UACD;CACD,GAAI;CACJ;;;;;;;;;;;;;;;;;;;AAqBJ,MAAM,gBAAgB,IACpB,koBACA;CACE,UAAU;;AAER,SAAS;;EAEP,OAAO;;EAEP,SAAS;;EAET,SAAS;EACV,EACF;CACD,iBAAiB,EACf,SAAS,WACV;CACF,CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCD,MAAM,SAGD,EAAE,WAAW,SAAS,GAAG,YAAY;AACxC,QACE,oBAAC,gBAAgB,MAAjB;EACE,WAAW,GAAG,cAAc,EAAE,SAAS,CAAC,EAAE,UAAU;EACpD,GAAI;EACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCN,MAAM,eAAkE,EACtE,WACA,GAAG,YAEH,oBAAC,gBAAgB,QAAjB;CACE,WAAW,GACT,wdACA,UACD;CACD,GAAI;CACJ;AAGJ,MAAM,cAAgE,EACpE,WACA,GAAG,YAEH,oBAAC,gBAAgB,OAAjB;CACE,WAAW,GACT,kVACA,UACD;CACD,eAAY;CACZ,GAAI;WAEJ,oBAAC,GAAD,EAAG,WAAU,UAAW;CACF;;;;;;;;;;;;;;;;;;;AAoB1B,MAAM,cAAgE,EACpE,WACA,GAAG,YAEH,oBAAC,gBAAgB,OAAjB;CACE,WAAW,GAAG,yCAAyC,UAAU;CACjE,GAAI;CACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BJ,MAAM,oBAED,EAAE,WAAW,GAAG,YACnB,oBAAC,gBAAgB,aAAjB;CACE,WAAW,GAAG,sBAAsB,UAAU;CAC9C,GAAI;CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/components/WithResizer/index.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport React, {\n type FC,\n type PropsWithChildren,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\n\n/**\n * Props for the WithResizer component.\n *\n * Defines the configuration for a resizable container with drag-based width adjustment.\n *\n * @example\n * ```tsx\n * // Basic resizable container\n * <WithResizer initialWidth={300} minWidth={200} maxWidth={600}>\n * <div className=\"p-4\">Resizable content</div>\n * </WithResizer>\n *\n * // Sidebar with resizing\n * <WithResizer\n * initialWidth={250}\n * minWidth={180}\n * maxWidth={400}\n * >\n * <nav className=\"h-full p-4\">\n * <SidebarContent />\n * </nav>\n * </WithResizer>\n *\n * // Panel with unlimited growth\n * <WithResizer initialWidth={400} minWidth={300}>\n * <div className=\"h-full overflow-auto\">\n * <PanelContent />\n * </div>\n * </WithResizer>\n * ```\n */\ntype WithResizerProps = {\n /** Initial width of the resizable container in pixels */\n initialWidth: number;\n /** Maximum allowed width in pixels (optional, no limit if not specified) */\n maxWidth?: number;\n /** Minimum allowed width in pixels */\n minWidth?: number;\n /** Position of the resize handle (default: 'right') */\n handlePosition?: 'left' | 'right';\n};\n\n/**\n * WithResizer Component\n *\n * A flexible container component that allows users to dynamically resize its width\n * through mouse or touch drag interactions. Perfect for creating adjustable panels,\n * sidebars, and split-pane layouts.\n *\n * ## Features\n * - **Mouse & Touch Support**: Works with both mouse drag and touch interactions\n * - **Constraint Enforcement**: Respects minimum and maximum width boundaries\n * - **Visual Feedback**: Clear resize handle with hover and active states\n * - **Smooth Interactions**: Passive event listeners for optimal performance\n * - **Accessibility**: ARIA slider role with proper value announcements\n * - **Responsive Design**: Adapts to different screen sizes and containers\n *\n * ## Technical Implementation\n * - **Event Handling**: Uses `useCallback` for optimal performance\n * - **Boundary Calculation**: Real-time width calculation based on mouse/touch position\n * - **State Management**: Tracks resizing state for visual feedback\n * - **Memory Management**: Proper cleanup of global event listeners\n * - **Touch Events**: Full support for mobile touch interactions\n *\n * ## Visual Design\n * - **Resize Handle**: Rounded handle positioned on the right border\n * - **Border Indicator**: Visual border showing resizable edge\n * - **State Feedback**: Different colors for normal, hover, and active states\n * - **Dark Mode**: Full support with appropriate color scheme\n * - **Smooth Transitions**: CSS transitions for visual polish\n *\n * ## Use Cases\n * - **Application Sidebars**: Collapsible navigation and tool panels\n * - **Content Panels**: Adjustable content areas in complex layouts\n * - **Split Panes**: Dividing screen space between multiple content areas\n * - **Inspector Panels**: Debugging tools and property inspectors\n * - **File Explorers**: Tree views with adjustable column widths\n * - **Dashboard Widgets**: Customizable widget sizes for dashboards\n *\n * ## Accessibility Features\n * - **ARIA Slider**: Proper slider role for screen readers\n * - **Value Announcements**: Current, minimum, and maximum values announced\n * - **Keyboard Focus**: Focusable with tab navigation\n * - **Clear Affordances**: Visual indicators for interactive elements\n *\n * @example\n * ```tsx\n * // Application sidebar with resizing\n * const [sidebarWidth, setSidebarWidth] = useState(250);\n *\n * <div className=\"flex h-screen\">\n * <WithResizer\n * initialWidth={sidebarWidth}\n * minWidth={200}\n * maxWidth={400}\n * >\n * <aside className=\"h-full bg-gray-100 p-4\">\n * <nav>\n * <NavItems />\n * </nav>\n * </aside>\n * </WithResizer>\n *\n * <main className=\"flex-1 p-6\">\n * <MainContent />\n * </main>\n * </div>\n *\n * // Developer tools panel\n * <WithResizer\n * initialWidth={350}\n * minWidth={250}\n * maxWidth={600}\n * >\n * <div className=\"h-full flex flex-col\">\n * <div className=\"flex-1 overflow-auto p-4\">\n * <InspectorContent />\n * </div>\n * <div className=\"border-t p-2\">\n * <Controls />\n * </div>\n * </div>\n * </WithResizer>\n *\n * // Multi-column layout\n * <div className=\"flex h-full\">\n * <WithResizer initialWidth={300} minWidth={200} maxWidth={500}>\n * <FileExplorer />\n * </WithResizer>\n *\n * <WithResizer initialWidth={400} minWidth={300}>\n * <CodeEditor />\n * </WithResizer>\n *\n * <div className=\"flex-1 min-w-0\">\n * <OutputPanel />\n * </div>\n * </div>\n * ```\n *\n * ## Performance Considerations\n * - Uses passive event listeners to prevent scroll blocking\n * - Optimized with `useCallback` to prevent unnecessary re-renders\n * - Efficient boundary calculations using `getBoundingClientRect`\n * - Minimal DOM manipulation for smooth drag interactions\n *\n * ## Browser Support\n * - Modern browsers with support for touch events\n * - Graceful degradation for older browsers\n * - Mobile-first touch interaction handling\n */\nexport const WithResizer: FC<PropsWithChildren<WithResizerProps>> = ({\n initialWidth,\n maxWidth,\n minWidth = 0,\n handlePosition = 'right',\n children,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState(initialWidth);\n\n const resizeState = useRef({\n startX: 0,\n startWidth: 0,\n factor: 1,\n });\n\n // Handler to resize the div\n const resize = useCallback(\n (mouseMoveEvent: MouseEvent | TouchEvent) => {\n if (resizeState.current.startWidth === 0) return;\n\n let clientX = 0;\n if (mouseMoveEvent instanceof MouseEvent) {\n clientX = mouseMoveEvent.clientX;\n } else if (\n typeof TouchEvent !== 'undefined' &&\n mouseMoveEvent instanceof TouchEvent\n ) {\n clientX = mouseMoveEvent.touches[0].clientX;\n }\n\n const { startX, startWidth, factor } = resizeState.current;\n const delta = (clientX - startX) / factor;\n // Invert delta for left handle (moving left decreases width, moving right increases width)\n const adjustedDelta = handlePosition === 'left' ? -delta : delta;\n const newWidth = startWidth + adjustedDelta;\n\n const constrainedWidth = Math.max(\n Math.min(newWidth, maxWidth ?? Infinity),\n minWidth\n );\n\n setWidth(constrainedWidth);\n },\n [maxWidth, minWidth, handlePosition]\n );\n\n // Handler to stop resizing\n const stopResizing = useCallback(() => {\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n window.removeEventListener('mousemove', resize);\n window.removeEventListener('mouseup', stopResizing);\n window.removeEventListener('touchmove', resize);\n window.removeEventListener('touchend', stopResizing);\n }, [resize]);\n\n // Handler to start resizing\n const startResizing = useCallback(\n (\n mouseDownEvent:\n | React.MouseEvent<HTMLDivElement>\n | React.TouchEvent<HTMLDivElement>\n ) => {\n mouseDownEvent.preventDefault();\n const container = containerRef.current;\n\n if (!container) return;\n\n const { width: rectWidth } = container.getBoundingClientRect();\n const offsetWidth = container.offsetWidth;\n const factor = offsetWidth > 0 ? rectWidth / offsetWidth : 1;\n\n let clientX = 0;\n if ('touches' in mouseDownEvent) {\n clientX = mouseDownEvent.touches[0].clientX;\n } else {\n clientX = mouseDownEvent.clientX;\n }\n\n resizeState.current = {\n startX: clientX,\n startWidth: offsetWidth,\n factor,\n };\n\n document.body.style.cursor = 'ew-resize';\n document.body.style.userSelect = 'none';\n\n window.addEventListener('mousemove', resize, { passive: true });\n window.addEventListener('mouseup', stopResizing);\n window.addEventListener('touchmove', resize, { passive: true });\n window.addEventListener('touchend', stopResizing);\n },\n [resize, stopResizing]\n );\n\n useEffect(() => {\n return () => {\n window.removeEventListener('mousemove', resize);\n window.removeEventListener('mouseup', stopResizing);\n window.removeEventListener('touchmove', resize);\n window.removeEventListener('touchend', stopResizing);\n };\n }, [resize, stopResizing]);\n\n return (\n <div\n className={cn(\n 'relative h-full w-full max-w-[80%] shrink-0 cursor-ew-resize border-neutral-200 transition dark:border-neutral-950',\n handlePosition === 'right'\n ? [\n 'border-r-[2px]',\n 'after:absolute after:top-1/2 after:right-0 after:block after:h-10 after:w-2 after:translate-x-1/2 after:-translate-y-1/2 after:transform after:cursor-ew-resize after:rounded-full after:bg-neutral-200 after:transition after:content-[\"\"] dark:after:bg-neutral-950',\n ]\n : [\n 'border-l-[2px]',\n 'after:absolute after:top-1/2 after:left-0 after:block after:h-10 after:w-2 after:-translate-x-1/2 after:-translate-y-1/2 after:transform after:cursor-ew-resize after:rounded-full after:bg-neutral-200 after:transition after:content-[\"\"] dark:after:bg-neutral-950',\n ],\n 'active:border-neutral-400 active:after:bg-neutral-400 dark:active:border-neutral-600 active:dark:after:bg-neutral-600',\n minWidth && `min-w-[${minWidth}px]`,\n maxWidth && `max-w-[${maxWidth}px]`\n )}\n style={{\n width: `${width}px`,\n }}\n ref={containerRef}\n onMouseDown={startResizing}\n onTouchStart={startResizing}\n aria-valuemin={minWidth}\n aria-valuemax={maxWidth}\n aria-valuenow={width}\n aria-label=\"Resizable component\"\n role=\"slider\"\n tabIndex={0}\n >\n {/* biome-ignore lint/a11y/noStaticElementInteractions: This div stops event propagation to prevent content clicks from triggering resize */}\n <div\n role=\"presentation\"\n className=\"absolute top-0 left-0 size-full cursor-default overflow-hidden\"\n onMouseDown={(e) => e.stopPropagation()}\n onTouchStart={(e) => e.stopPropagation()}\n >\n {children}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmKA,MAAa,eAAwD,EACnE,cACA,UACA,WAAW,GACX,iBAAiB,SACjB,eACI;CACJ,MAAM,eAAe,OAAuB,KAAK;CACjD,MAAM,CAAC,OAAO,YAAY,SAAS,aAAa;CAEhD,MAAM,cAAc,OAAO;EACzB,QAAQ;EACR,YAAY;EACZ,QAAQ;EACT,CAAC;CAGF,MAAM,SAAS,aACZ,mBAA4C;AAC3C,MAAI,YAAY,QAAQ,eAAe,EAAG;EAE1C,IAAI,UAAU;AACd,MAAI,0BAA0B,WAC5B,WAAU,eAAe;WAEzB,OAAO,eAAe,eACtB,0BAA0B,WAE1B,WAAU,eAAe,QAAQ,GAAG;EAGtC,MAAM,EAAE,QAAQ,YAAY,WAAW,YAAY;EACnD,MAAM,SAAS,UAAU,UAAU;EAGnC,MAAM,WAAW,cADK,mBAAmB,SAAS,CAAC,QAAQ;AAQ3D,WALyB,KAAK,IAC5B,KAAK,IAAI,UAAU,YAAY,SAAS,EACxC,SACD,CAEyB;IAE5B;EAAC;EAAU;EAAU;EAAe,CACrC;CAGD,MAAM,eAAe,kBAAkB;AACrC,WAAS,KAAK,MAAM,SAAS;AAC7B,WAAS,KAAK,MAAM,aAAa;AACjC,SAAO,oBAAoB,aAAa,OAAO;AAC/C,SAAO,oBAAoB,WAAW,aAAa;AACnD,SAAO,oBAAoB,aAAa,OAAO;AAC/C,SAAO,oBAAoB,YAAY,aAAa;IACnD,CAAC,OAAO,CAAC;CAGZ,MAAM,gBAAgB,aAElB,mBAGG;AACH,iBAAe,gBAAgB;EAC/B,MAAM,YAAY,aAAa;AAE/B,MAAI,CAAC,UAAW;EAEhB,MAAM,EAAE,OAAO,cAAc,UAAU,uBAAuB;EAC9D,MAAM,cAAc,UAAU;EAC9B,MAAM,SAAS,cAAc,IAAI,YAAY,cAAc;EAE3D,IAAI,UAAU;AACd,MAAI,aAAa,eACf,WAAU,eAAe,QAAQ,GAAG;MAEpC,WAAU,eAAe;AAG3B,cAAY,UAAU;GACpB,QAAQ;GACR,YAAY;GACZ;GACD;AAED,WAAS,KAAK,MAAM,SAAS;AAC7B,WAAS,KAAK,MAAM,aAAa;AAEjC,SAAO,iBAAiB,aAAa,QAAQ,EAAE,SAAS,MAAM,CAAC;AAC/D,SAAO,iBAAiB,WAAW,aAAa;AAChD,SAAO,iBAAiB,aAAa,QAAQ,EAAE,SAAS,MAAM,CAAC;AAC/D,SAAO,iBAAiB,YAAY,aAAa;IAEnD,CAAC,QAAQ,aAAa,CACvB;AAED,iBAAgB;AACd,eAAa;AACX,UAAO,oBAAoB,aAAa,OAAO;AAC/C,UAAO,oBAAoB,WAAW,aAAa;AACnD,UAAO,oBAAoB,aAAa,OAAO;AAC/C,UAAO,oBAAoB,YAAY,aAAa;;IAErD,CAAC,QAAQ,aAAa,CAAC;AAE1B,QACE,oBAAC,OAAD;EACE,WAAW,GACT,sHACA,mBAAmB,UACf,CACE,kBACA,0QACD,GACD,CACE,kBACA,0QACD,EACL,yHACA,YAAY,UAAU,SAAS,MAC/B,YAAY,UAAU,SAAS,KAChC;EACD,OAAO,EACL,OAAO,GAAG,MAAM,KACjB;EACD,KAAK;EACL,aAAa;EACb,cAAc;EACd,iBAAe;EACf,iBAAe;EACf,iBAAe;EACf,cAAW;EACX,MAAK;EACL,UAAU;YAGV,oBAAC,OAAD;GACE,MAAK;GACL,WAAU;GACV,cAAc,MAAM,EAAE,iBAAiB;GACvC,eAAe,MAAM,EAAE,iBAAiB;GAEvC;GACG;EACF"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/components/WithResizer/index.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport React, {\n type FC,\n type PropsWithChildren,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\n\n/**\n * Props for the WithResizer component.\n *\n * Defines the configuration for a resizable container with drag-based width adjustment.\n *\n * @example\n * ```tsx\n * // Basic resizable container\n * <WithResizer initialWidth={300} minWidth={200} maxWidth={600}>\n * <div className=\"p-4\">Resizable content</div>\n * </WithResizer>\n *\n * // Sidebar with resizing\n * <WithResizer\n * initialWidth={250}\n * minWidth={180}\n * maxWidth={400}\n * >\n * <nav className=\"h-full p-4\">\n * <SidebarContent />\n * </nav>\n * </WithResizer>\n *\n * // Panel with unlimited growth\n * <WithResizer initialWidth={400} minWidth={300}>\n * <div className=\"h-full overflow-auto\">\n * <PanelContent />\n * </div>\n * </WithResizer>\n * ```\n */\ntype WithResizerProps = {\n /** Initial width of the resizable container in pixels */\n initialWidth: number;\n /** Maximum allowed width in pixels (optional, no limit if not specified) */\n maxWidth?: number;\n /** Minimum allowed width in pixels */\n minWidth?: number;\n /** Position of the resize handle (default: 'right') */\n handlePosition?: 'left' | 'right';\n};\n\n/**\n * WithResizer Component\n *\n * A flexible container component that allows users to dynamically resize its width\n * through mouse or touch drag interactions. Perfect for creating adjustable panels,\n * sidebars, and split-pane layouts.\n *\n * ## Features\n * - **Mouse & Touch Support**: Works with both mouse drag and touch interactions\n * - **Constraint Enforcement**: Respects minimum and maximum width boundaries\n * - **Visual Feedback**: Clear resize handle with hover and active states\n * - **Smooth Interactions**: Passive event listeners for optimal performance\n * - **Accessibility**: ARIA slider role with proper value announcements\n * - **Responsive Design**: Adapts to different screen sizes and containers\n *\n * ## Technical Implementation\n * - **Event Handling**: Uses `useCallback` for optimal performance\n * - **Boundary Calculation**: Real-time width calculation based on mouse/touch position\n * - **State Management**: Tracks resizing state for visual feedback\n * - **Memory Management**: Proper cleanup of global event listeners\n * - **Touch Events**: Full support for mobile touch interactions\n *\n * ## Visual Design\n * - **Resize Handle**: Rounded handle positioned on the right border\n * - **Border Indicator**: Visual border showing resizable edge\n * - **State Feedback**: Different colors for normal, hover, and active states\n * - **Dark Mode**: Full support with appropriate color scheme\n * - **Smooth Transitions**: CSS transitions for visual polish\n *\n * ## Use Cases\n * - **Application Sidebars**: Collapsible navigation and tool panels\n * - **Content Panels**: Adjustable content areas in complex layouts\n * - **Split Panes**: Dividing screen space between multiple content areas\n * - **Inspector Panels**: Debugging tools and property inspectors\n * - **File Explorers**: Tree views with adjustable column widths\n * - **Dashboard Widgets**: Customizable widget sizes for dashboards\n *\n * ## Accessibility Features\n * - **ARIA Slider**: Proper slider role for screen readers\n * - **Value Announcements**: Current, minimum, and maximum values announced\n * - **Keyboard Focus**: Focusable with tab navigation\n * - **Clear Affordances**: Visual indicators for interactive elements\n *\n * @example\n * ```tsx\n * // Application sidebar with resizing\n * const [sidebarWidth, setSidebarWidth] = useState(250);\n *\n * <div className=\"flex h-screen\">\n * <WithResizer\n * initialWidth={sidebarWidth}\n * minWidth={200}\n * maxWidth={400}\n * >\n * <aside className=\"h-full bg-gray-100 p-4\">\n * <nav>\n * <NavItems />\n * </nav>\n * </aside>\n * </WithResizer>\n *\n * <main className=\"flex-1 p-6\">\n * <MainContent />\n * </main>\n * </div>\n *\n * // Developer tools panel\n * <WithResizer\n * initialWidth={350}\n * minWidth={250}\n * maxWidth={600}\n * >\n * <div className=\"h-full flex flex-col\">\n * <div className=\"flex-1 overflow-auto p-4\">\n * <InspectorContent />\n * </div>\n * <div className=\"border-t p-2\">\n * <Controls />\n * </div>\n * </div>\n * </WithResizer>\n *\n * // Multi-column layout\n * <div className=\"flex h-full\">\n * <WithResizer initialWidth={300} minWidth={200} maxWidth={500}>\n * <FileExplorer />\n * </WithResizer>\n *\n * <WithResizer initialWidth={400} minWidth={300}>\n * <CodeEditor />\n * </WithResizer>\n *\n * <div className=\"flex-1 min-w-0\">\n * <OutputPanel />\n * </div>\n * </div>\n * ```\n *\n * ## Performance Considerations\n * - Uses passive event listeners to prevent scroll blocking\n * - Optimized with `useCallback` to prevent unnecessary re-renders\n * - Efficient boundary calculations using `getBoundingClientRect`\n * - Minimal DOM manipulation for smooth drag interactions\n *\n * ## Browser Support\n * - Modern browsers with support for touch events\n * - Graceful degradation for older browsers\n * - Mobile-first touch interaction handling\n */\nexport const WithResizer: FC<PropsWithChildren<WithResizerProps>> = ({\n initialWidth,\n maxWidth,\n minWidth = 0,\n handlePosition = 'right',\n children,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState(initialWidth);\n\n const resizeState = useRef({\n startX: 0,\n startWidth: 0,\n factor: 1,\n });\n\n // Handler to resize the div\n const resize = useCallback(\n (mouseMoveEvent: MouseEvent | TouchEvent) => {\n if (resizeState.current.startWidth === 0) return;\n\n let clientX = 0;\n if (mouseMoveEvent instanceof MouseEvent) {\n clientX = mouseMoveEvent.clientX;\n } else if (\n typeof TouchEvent !== 'undefined' &&\n mouseMoveEvent instanceof TouchEvent\n ) {\n clientX = mouseMoveEvent.touches[0].clientX;\n }\n\n const { startX, startWidth, factor } = resizeState.current;\n const delta = (clientX - startX) / factor;\n // Invert delta for left handle (moving left decreases width, moving right increases width)\n const adjustedDelta = handlePosition === 'left' ? -delta : delta;\n const newWidth = startWidth + adjustedDelta;\n\n const constrainedWidth = Math.max(\n Math.min(newWidth, maxWidth ?? Infinity),\n minWidth\n );\n\n setWidth(constrainedWidth);\n },\n [maxWidth, minWidth, handlePosition]\n );\n\n // Handler to stop resizing\n const stopResizing = useCallback(() => {\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n window.removeEventListener('mousemove', resize);\n window.removeEventListener('mouseup', stopResizing);\n window.removeEventListener('touchmove', resize);\n window.removeEventListener('touchend', stopResizing);\n }, [resize]);\n\n // Handler to start resizing\n const startResizing = useCallback(\n (\n mouseDownEvent:\n | React.MouseEvent<HTMLDivElement>\n | React.TouchEvent<HTMLDivElement>\n ) => {\n mouseDownEvent.preventDefault();\n const container = containerRef.current;\n\n if (!container) return;\n\n const { width: rectWidth } = container.getBoundingClientRect();\n const offsetWidth = container.offsetWidth;\n const factor = offsetWidth > 0 ? rectWidth / offsetWidth : 1;\n\n let clientX = 0;\n if ('touches' in mouseDownEvent) {\n clientX = mouseDownEvent.touches[0].clientX;\n } else {\n clientX = mouseDownEvent.clientX;\n }\n\n resizeState.current = {\n startX: clientX,\n startWidth: offsetWidth,\n factor,\n };\n\n document.body.style.cursor = 'ew-resize';\n document.body.style.userSelect = 'none';\n\n window.addEventListener('mousemove', resize, { passive: true });\n window.addEventListener('mouseup', stopResizing);\n window.addEventListener('touchmove', resize, { passive: true });\n window.addEventListener('touchend', stopResizing);\n },\n [resize, stopResizing]\n );\n\n useEffect(() => {\n return () => {\n window.removeEventListener('mousemove', resize);\n window.removeEventListener('mouseup', stopResizing);\n window.removeEventListener('touchmove', resize);\n window.removeEventListener('touchend', stopResizing);\n };\n }, [resize, stopResizing]);\n\n return (\n <div\n className={cn(\n 'relative h-full w-full max-w-[80%] shrink-0 cursor-ew-resize border-neutral-200 transition dark:border-neutral-950',\n handlePosition === 'right'\n ? [\n 'border-r-[2px]',\n 'after:absolute after:top-1/2 after:right-0 after:block after:h-10 after:w-2 after:translate-x-1/2 after:-translate-y-1/2 after:transform after:cursor-ew-resize after:rounded-full after:bg-neutral-200 after:transition after:content-[\"\"] dark:after:bg-neutral-950',\n ]\n : [\n 'border-l-[2px]',\n 'after:absolute after:top-1/2 after:left-0 after:block after:h-10 after:w-2 after:-translate-x-1/2 after:-translate-y-1/2 after:transform after:cursor-ew-resize after:rounded-full after:bg-neutral-200 after:transition after:content-[\"\"] dark:after:bg-neutral-950',\n ],\n 'active:border-neutral-400 active:after:bg-neutral-400 dark:active:border-neutral-600 active:dark:after:bg-neutral-600',\n minWidth && `min-w-[${minWidth}px]`,\n maxWidth && `max-w-[${maxWidth}px]`\n )}\n style={{\n width: `${width}px`,\n }}\n ref={containerRef}\n onMouseDown={startResizing}\n onTouchStart={startResizing}\n aria-valuemin={minWidth}\n aria-valuemax={maxWidth}\n aria-valuenow={width}\n aria-label=\"Resizable component\"\n role=\"slider\"\n tabIndex={0}\n >\n {/* biome-ignore lint/a11y/noStaticElementInteractions: This div stops event propagation to prevent content clicks from triggering resize */}\n <div\n role=\"presentation\"\n className=\"absolute top-0 left-0 size-full cursor-default overflow-hidden\"\n onMouseDown={(e) => e.stopPropagation()}\n onTouchStart={(e) => e.stopPropagation()}\n >\n {children}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmKA,MAAa,eAAwD,EACnE,cACA,UACA,WAAW,GACX,iBAAiB,SACjB,eACI;CACJ,MAAM,eAAe,OAAuB,KAAK;CACjD,MAAM,CAAC,OAAO,YAAY,SAAS,aAAa;CAEhD,MAAM,cAAc,OAAO;EACzB,QAAQ;EACR,YAAY;EACZ,QAAQ;EACT,CAAC;CAGF,MAAM,SAAS,aACZ,mBAA4C;AAC3C,MAAI,YAAY,QAAQ,eAAe,EAAG;EAE1C,IAAI,UAAU;AACd,MAAI,0BAA0B,WAC5B,WAAU,eAAe;WAEzB,OAAO,eAAe,eACtB,0BAA0B,WAE1B,WAAU,eAAe,QAAQ,GAAG;EAGtC,MAAM,EAAE,QAAQ,YAAY,WAAW,YAAY;EACnD,MAAM,SAAS,UAAU,UAAU;EAGnC,MAAM,WAAW,cADK,mBAAmB,SAAS,CAAC,QAAQ;AAQ3D,WALyB,KAAK,IAC5B,KAAK,IAAI,UAAU,YAAY,SAAS,EACxC,SAGuB,CAAC;IAE5B;EAAC;EAAU;EAAU;EAAe,CACrC;CAGD,MAAM,eAAe,kBAAkB;AACrC,WAAS,KAAK,MAAM,SAAS;AAC7B,WAAS,KAAK,MAAM,aAAa;AACjC,SAAO,oBAAoB,aAAa,OAAO;AAC/C,SAAO,oBAAoB,WAAW,aAAa;AACnD,SAAO,oBAAoB,aAAa,OAAO;AAC/C,SAAO,oBAAoB,YAAY,aAAa;IACnD,CAAC,OAAO,CAAC;CAGZ,MAAM,gBAAgB,aAElB,mBAGG;AACH,iBAAe,gBAAgB;EAC/B,MAAM,YAAY,aAAa;AAE/B,MAAI,CAAC,UAAW;EAEhB,MAAM,EAAE,OAAO,cAAc,UAAU,uBAAuB;EAC9D,MAAM,cAAc,UAAU;EAC9B,MAAM,SAAS,cAAc,IAAI,YAAY,cAAc;EAE3D,IAAI,UAAU;AACd,MAAI,aAAa,eACf,WAAU,eAAe,QAAQ,GAAG;MAEpC,WAAU,eAAe;AAG3B,cAAY,UAAU;GACpB,QAAQ;GACR,YAAY;GACZ;GACD;AAED,WAAS,KAAK,MAAM,SAAS;AAC7B,WAAS,KAAK,MAAM,aAAa;AAEjC,SAAO,iBAAiB,aAAa,QAAQ,EAAE,SAAS,MAAM,CAAC;AAC/D,SAAO,iBAAiB,WAAW,aAAa;AAChD,SAAO,iBAAiB,aAAa,QAAQ,EAAE,SAAS,MAAM,CAAC;AAC/D,SAAO,iBAAiB,YAAY,aAAa;IAEnD,CAAC,QAAQ,aAAa,CACvB;AAED,iBAAgB;AACd,eAAa;AACX,UAAO,oBAAoB,aAAa,OAAO;AAC/C,UAAO,oBAAoB,WAAW,aAAa;AACnD,UAAO,oBAAoB,aAAa,OAAO;AAC/C,UAAO,oBAAoB,YAAY,aAAa;;IAErD,CAAC,QAAQ,aAAa,CAAC;AAE1B,QACE,oBAAC,OAAD;EACE,WAAW,GACT,sHACA,mBAAmB,UACf,CACE,kBACA,0QACD,GACD,CACE,kBACA,0QACD,EACL,yHACA,YAAY,UAAU,SAAS,MAC/B,YAAY,UAAU,SAAS,KAChC;EACD,OAAO,EACL,OAAO,GAAG,MAAM,KACjB;EACD,KAAK;EACL,aAAa;EACb,cAAc;EACd,iBAAe;EACf,iBAAe;EACf,iBAAe;EACf,cAAW;EACX,MAAK;EACL,UAAU;YAGV,oBAAC,OAAD;GACE,MAAK;GACL,WAAU;GACV,cAAc,MAAM,EAAE,iBAAiB;GACvC,eAAe,MAAM,EAAE,iBAAiB;GAEvC;GACG;EACF"}
|
package/dist/esm/hooks/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { useOAuth2 } from "./useAuth/useOAuth2.mjs";
|
|
|
5
5
|
import { useSession } from "./useAuth/useSession.mjs";
|
|
6
6
|
import { useAuth } from "./useAuth/useAuth.mjs";
|
|
7
7
|
import { useIntlayerAuth, useIntlayerOAuth } from "./useIntlayerAPI.mjs";
|
|
8
|
-
import { useAddDictionary, useAddNewAccessKey, useAddOrganization, useAddOrganizationMember, useAddPasskey, useAddProject, useAddTag, useAppQuery, useAskDocQuestion, useAskResetPassword, useAuditContentDeclaration, useAuditContentDeclarationField, useAuditContentDeclarationMetadata, useAuditScan, useAuditTag, useAutocomplete, useBitbucketAuth, useBitbucketCheckConfig, useBitbucketGetConfigFile, useBitbucketRepos, useCancelSubscription, useChangePassword, useCreateUser, useDeleteAccessKey, useDeleteDictionary, useDeleteOrganization, useDeletePasskey, useDeleteProject, useDeleteSSOProvider, useDeleteShowcaseProject, useDeleteTag, useDeleteUser, useDisableTwoFactor, useEnableTwoFactor, useGetCIConfig, useGetDictionaries, useGetDictionariesKeys, useGetDictionary, useGetDiscussions, useGetDiscussionsData, useGetEditorDictionaries, useGetNewsletterStatus, useGetOrganizations, useGetOtherShowcaseProjects, useGetPricing, useGetProjects, useGetRecursiveAuditStatus, useGetShowcaseProjectById, useGetShowcaseProjects, useGetSubscription, useGetTags, useGetUserByAccount, useGetUserById, useGetUsers, useGetVerifyEmailStatus, useGithubAuth, useGithubCheckConfig, useGithubGetAuthUrl, useGithubGetConfigFile, useGithubRepos, useGitlabAuth, useGitlabCheckConfig, useGitlabGetConfigFile, useGitlabProjects, useInfiniteGetDictionaries, useListPasskeys, useListSSOProviders, useLogin, useLogout, usePushCIConfig, usePushDictionaries, useQueryClient, useRefreshAccessKey, useRegister, useRegisterSSO, useResetPassword, useSearchDoc, useSelectOrganization, useSelectProject, useSignInMagicLink, useSignInPasskey, useSignInSSO, useStartRecursiveAudit, useSubmitShowcaseProject, useSubscribeToNewsletter, useToggleShowcaseDownvote, useToggleShowcaseUpvote, useTranslateJSONDeclaration, useTriggerBuild, useTriggerWebhook, useUnselectOrganization, useUnselectProject, useUnsubscribeFromNewsletter, useUpdateDictionary, useUpdateOrganization, useUpdateOrganizationMembers, useUpdateOrganizationMembersById, useUpdateProject, useUpdateProjectMembers, useUpdateShowcaseProject, useUpdateTag, useUpdateUser, useVerifyBackupCode, useVerifyEmail, useVerifyTotp, useWriteDictionary } from "./reactQuery.mjs";
|
|
8
|
+
import { useAddDictionary, useAddNewAccessKey, useAddOrganization, useAddOrganizationMember, useAddPasskey, useAddProject, useAddTag, useAppQuery, useAskDocQuestion, useAskResetPassword, useAuditContentDeclaration, useAuditContentDeclarationField, useAuditContentDeclarationMetadata, useAuditScan, useAuditTag, useAutocomplete, useBitbucketAuth, useBitbucketCheckConfig, useBitbucketGetConfigFile, useBitbucketRepos, useCancelSubscription, useChangePassword, useCreateUser, useDeleteAccessKey, useDeleteDictionary, useDeleteOrganization, useDeletePasskey, useDeleteProject, useDeleteSSOProvider, useDeleteShowcaseProject, useDeleteTag, useDeleteUser, useDisableTwoFactor, useEnableTwoFactor, useFillAllTranslations, useGetCIConfig, useGetDictionaries, useGetDictionariesKeys, useGetDictionary, useGetDiscussions, useGetDiscussionsData, useGetEditorDictionaries, useGetNewsletterStatus, useGetOrganizations, useGetOtherShowcaseProjects, useGetPricing, useGetProjects, useGetRecursiveAuditStatus, useGetShowcaseProjectById, useGetShowcaseProjects, useGetSubscription, useGetTags, useGetUserByAccount, useGetUserById, useGetUsers, useGetVerifyEmailStatus, useGithubAuth, useGithubCheckConfig, useGithubGetAuthUrl, useGithubGetConfigFile, useGithubRepos, useGitlabAuth, useGitlabCheckConfig, useGitlabGetConfigFile, useGitlabProjects, useInfiniteGetDictionaries, useListPasskeys, useListSSOProviders, useLogin, useLogout, usePauseTranslationJob, usePushCIConfig, usePushDictionaries, useQueryClient, useRefreshAccessKey, useRegister, useRegisterSSO, useResetPassword, useResumeTranslationJob, useSearchDoc, useSelectOrganization, useSelectProject, useSignInMagicLink, useSignInPasskey, useSignInSSO, useStartRecursiveAudit, useStopTranslationJob, useSubmitShowcaseProject, useSubscribeToNewsletter, useToggleShowcaseDownvote, useToggleShowcaseUpvote, useTranslateJSONDeclaration, useTriggerBuild, useTriggerWebhook, useUnselectOrganization, useUnselectProject, useUnsubscribeFromNewsletter, useUpdateDictionary, useUpdateOrganization, useUpdateOrganizationMembers, useUpdateOrganizationMembersById, useUpdateProject, useUpdateProjectMembers, useUpdateShowcaseProject, useUpdateTag, useUpdateUser, useVerifyBackupCode, useVerifyEmail, useVerifyTotp, useWriteDictionary } from "./reactQuery.mjs";
|
|
9
9
|
import { useUser } from "./useUser/index.mjs";
|
|
10
10
|
import { useHorizontalSwipe } from "./useHorizontalSwipe.mjs";
|
|
11
11
|
import { useGetElementById } from "./useGetElementById.mjs";
|
|
@@ -19,4 +19,4 @@ import { useScrollDetection } from "./useScrollDetection.mjs";
|
|
|
19
19
|
import { useScrollY } from "./useScrollY.mjs";
|
|
20
20
|
import { useSearch } from "./useSearch.mjs";
|
|
21
21
|
|
|
22
|
-
export { calculateIsMobile, checkIsIOS, checkIsIphoneOrSafariDevice, checkIsMac, checkIsMobileScreen, checkIsMobileUserAgent, getBreakpointFromSize, useAddDictionary, useAddNewAccessKey, useAddOrganization, useAddOrganizationMember, useAddPasskey, useAddProject, useAddTag, useAppQuery, useAskDocQuestion, useAskResetPassword, useAuditContentDeclaration, useAuditContentDeclarationField, useAuditContentDeclarationMetadata, useAuditScan, useAuditTag, useAuth, useAutocomplete, useBitbucketAuth, useBitbucketCheckConfig, useBitbucketGetConfigFile, useBitbucketRepos, useCancelSubscription, useChangePassword, useCreateUser, useDeleteAccessKey, useDeleteDictionary, useDeleteOrganization, useDeletePasskey, useDeleteProject, useDeleteSSOProvider, useDeleteShowcaseProject, useDeleteTag, useDeleteUser, useDevice, useDisableTwoFactor, useEnableTwoFactor, useGetCIConfig, useGetDictionaries, useGetDictionariesKeys, useGetDictionary, useGetDiscussions, useGetDiscussionsData, useGetEditorDictionaries, useGetElementById, useGetElementOrWindow, useGetNewsletterStatus, useGetOrganizations, useGetOtherShowcaseProjects, useGetPricing, useGetProjects, useGetRecursiveAuditStatus, useGetShowcaseProjectById, useGetShowcaseProjects, useGetSubscription, useGetTags, useGetUserByAccount, useGetUserById, useGetUsers, useGetVerifyEmailStatus, useGithubAuth, useGithubCheckConfig, useGithubGetAuthUrl, useGithubGetConfigFile, useGithubRepos, useGitlabAuth, useGitlabCheckConfig, useGitlabGetConfigFile, useGitlabProjects, useHorizontalSwipe, useInfiniteGetDictionaries, useIntlayerAuth, useIntlayerOAuth, useIsDarkMode, useIsMounted, useItemSelector, useKeyboardDetector, useListPasskeys, useListSSOProviders, useLogin, useLogout, useOAuth2, usePersistedStore, usePushCIConfig, usePushDictionaries, useQueryClient, useRefreshAccessKey, useRegister, useRegisterSSO, useResetPassword, useScreenWidth, useScrollBlockage, useScrollDetection, useScrollY, useSearch, useSearchDoc, useSelectOrganization, useSelectProject, useSession, useSignInMagicLink, useSignInPasskey, useSignInSSO, useStartRecursiveAudit, useSubmitShowcaseProject, useSubscribeToNewsletter, useToggleShowcaseDownvote, useToggleShowcaseUpvote, useTranslateJSONDeclaration, useTriggerBuild, useTriggerWebhook, useUnselectOrganization, useUnselectProject, useUnsubscribeFromNewsletter, useUpdateDictionary, useUpdateOrganization, useUpdateOrganizationMembers, useUpdateOrganizationMembersById, useUpdateProject, useUpdateProjectMembers, useUpdateShowcaseProject, useUpdateTag, useUpdateUser, useUser, useVerifyBackupCode, useVerifyEmail, useVerifyTotp, useWriteDictionary };
|
|
22
|
+
export { calculateIsMobile, checkIsIOS, checkIsIphoneOrSafariDevice, checkIsMac, checkIsMobileScreen, checkIsMobileUserAgent, getBreakpointFromSize, useAddDictionary, useAddNewAccessKey, useAddOrganization, useAddOrganizationMember, useAddPasskey, useAddProject, useAddTag, useAppQuery, useAskDocQuestion, useAskResetPassword, useAuditContentDeclaration, useAuditContentDeclarationField, useAuditContentDeclarationMetadata, useAuditScan, useAuditTag, useAuth, useAutocomplete, useBitbucketAuth, useBitbucketCheckConfig, useBitbucketGetConfigFile, useBitbucketRepos, useCancelSubscription, useChangePassword, useCreateUser, useDeleteAccessKey, useDeleteDictionary, useDeleteOrganization, useDeletePasskey, useDeleteProject, useDeleteSSOProvider, useDeleteShowcaseProject, useDeleteTag, useDeleteUser, useDevice, useDisableTwoFactor, useEnableTwoFactor, useFillAllTranslations, useGetCIConfig, useGetDictionaries, useGetDictionariesKeys, useGetDictionary, useGetDiscussions, useGetDiscussionsData, useGetEditorDictionaries, useGetElementById, useGetElementOrWindow, useGetNewsletterStatus, useGetOrganizations, useGetOtherShowcaseProjects, useGetPricing, useGetProjects, useGetRecursiveAuditStatus, useGetShowcaseProjectById, useGetShowcaseProjects, useGetSubscription, useGetTags, useGetUserByAccount, useGetUserById, useGetUsers, useGetVerifyEmailStatus, useGithubAuth, useGithubCheckConfig, useGithubGetAuthUrl, useGithubGetConfigFile, useGithubRepos, useGitlabAuth, useGitlabCheckConfig, useGitlabGetConfigFile, useGitlabProjects, useHorizontalSwipe, useInfiniteGetDictionaries, useIntlayerAuth, useIntlayerOAuth, useIsDarkMode, useIsMounted, useItemSelector, useKeyboardDetector, useListPasskeys, useListSSOProviders, useLogin, useLogout, useOAuth2, usePauseTranslationJob, usePersistedStore, usePushCIConfig, usePushDictionaries, useQueryClient, useRefreshAccessKey, useRegister, useRegisterSSO, useResetPassword, useResumeTranslationJob, useScreenWidth, useScrollBlockage, useScrollDetection, useScrollY, useSearch, useSearchDoc, useSelectOrganization, useSelectProject, useSession, useSignInMagicLink, useSignInPasskey, useSignInSSO, useStartRecursiveAudit, useStopTranslationJob, useSubmitShowcaseProject, useSubscribeToNewsletter, useToggleShowcaseDownvote, useToggleShowcaseUpvote, useTranslateJSONDeclaration, useTriggerBuild, useTriggerWebhook, useUnselectOrganization, useUnselectProject, useUnsubscribeFromNewsletter, useUpdateDictionary, useUpdateOrganization, useUpdateOrganizationMembers, useUpdateOrganizationMembersById, useUpdateProject, useUpdateProjectMembers, useUpdateShowcaseProject, useUpdateTag, useUpdateUser, useUser, useVerifyBackupCode, useVerifyEmail, useVerifyTotp, useWriteDictionary };
|