@keboola/design 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -0
- package/package.json +151 -0
- package/src/blocks/AIKit/AnswerSummary.stories.tsx +38 -0
- package/src/blocks/AIKit/AnswerSummary.tsx +63 -0
- package/src/blocks/AIKit/CollapsibleStepHeader.stories.tsx +53 -0
- package/src/blocks/AIKit/CollapsibleStepHeader.tsx +65 -0
- package/src/blocks/AIKit/InlineStepDetail.stories.tsx +25 -0
- package/src/blocks/AIKit/InlineStepDetail.tsx +13 -0
- package/src/blocks/AIKit/InlineStepLabel.stories.tsx +37 -0
- package/src/blocks/AIKit/InlineStepLabel.tsx +36 -0
- package/src/blocks/AIKit/MessageBubble.stories.tsx +137 -0
- package/src/blocks/AIKit/MessageBubble.tsx +68 -0
- package/src/blocks/AIKit/PlanApproval.stories.tsx +121 -0
- package/src/blocks/AIKit/PlanApproval.test.tsx +241 -0
- package/src/blocks/AIKit/PlanApproval.tsx +345 -0
- package/src/blocks/AIKit/PlanRewritingIndicator.test.tsx +17 -0
- package/src/blocks/AIKit/PlanRewritingIndicator.tsx +20 -0
- package/src/blocks/AIKit/PromptInput.stories.tsx +98 -0
- package/src/blocks/AIKit/PromptInput.tsx +218 -0
- package/src/blocks/AIKit/PromptInputAction.tsx +18 -0
- package/src/blocks/AIKit/PromptInputSubmit.tsx +55 -0
- package/src/blocks/AIKit/Question.stories.tsx +235 -0
- package/src/blocks/AIKit/Question.tsx +323 -0
- package/src/blocks/AIKit/QuestionBlock.stories.tsx +99 -0
- package/src/blocks/AIKit/QuestionBlock.tsx +83 -0
- package/src/blocks/AIKit/SuggestedPrompt.stories.tsx +88 -0
- package/src/blocks/AIKit/SuggestedPrompt.tsx +35 -0
- package/src/blocks/AIKit/ThinkingIndicator.stories.tsx +21 -0
- package/src/blocks/AIKit/ThinkingIndicator.tsx +14 -0
- package/src/blocks/AIKit/index.ts +12 -0
- package/src/blocks/AutoSaveTextarea/AutoSaveTextarea.stories.tsx +78 -0
- package/src/blocks/AutoSaveTextarea/AutoSaveTextarea.tsx +103 -0
- package/src/blocks/AutoSaveTextarea/index.ts +1 -0
- package/src/blocks/ClipboardButton/ClipboardButton.stories.tsx +28 -0
- package/src/blocks/ClipboardButton/ClipboardButton.tsx +41 -0
- package/src/blocks/ClipboardButton/ClipboardInline.tsx +41 -0
- package/src/blocks/ClipboardButton/index.ts +2 -0
- package/src/blocks/ColorInput/ColorInput.stories.tsx +109 -0
- package/src/blocks/ColorInput/ColorInput.test.tsx +163 -0
- package/src/blocks/ColorInput/ColorInput.tsx +169 -0
- package/src/blocks/ColorInput/ColorPicker.tsx +39 -0
- package/src/blocks/ColorInput/index.ts +1 -0
- package/src/blocks/ColorInput/utils.test.ts +189 -0
- package/src/blocks/ColorInput/utils.ts +74 -0
- package/src/blocks/EmptyConfigurationActionCard/EmptyConfigurationActionCard.stories.tsx +30 -0
- package/src/blocks/EmptyConfigurationActionCard/EmptyConfigurationActionCard.tsx +20 -0
- package/src/blocks/EmptyConfigurationActionCard/index.ts +1 -0
- package/src/blocks/EmptyState/EmptyState.stories.tsx +93 -0
- package/src/blocks/EmptyState/EmptyState.tsx +130 -0
- package/src/blocks/EmptyState/index.ts +1 -0
- package/src/blocks/ErrorState/ErrorState.stories.tsx +125 -0
- package/src/blocks/ErrorState/ErrorState.tsx +132 -0
- package/src/blocks/ErrorState/index.ts +1 -0
- package/src/blocks/Form/components/FormBody.tsx +27 -0
- package/src/blocks/Form/components/SubmitButton.tsx +30 -0
- package/src/blocks/Form/connectors/CheckboxField.tsx +34 -0
- package/src/blocks/Form/connectors/FieldShell.tsx +46 -0
- package/src/blocks/Form/connectors/NumberField.tsx +36 -0
- package/src/blocks/Form/connectors/PasswordField.tsx +38 -0
- package/src/blocks/Form/connectors/SelectField.tsx +73 -0
- package/src/blocks/Form/connectors/SwitchField.tsx +34 -0
- package/src/blocks/Form/connectors/TextAreaField.tsx +36 -0
- package/src/blocks/Form/connectors/TextField.tsx +36 -0
- package/src/blocks/Form/contexts.ts +4 -0
- package/src/blocks/Form/formHook.ts +30 -0
- package/src/blocks/Form/index.ts +2 -0
- package/src/blocks/Form/setServerFieldErrors.test.ts +38 -0
- package/src/blocks/Form/setServerFieldErrors.ts +21 -0
- package/src/blocks/MermaidDiagram/MermaidDiagram.stories.tsx +87 -0
- package/src/blocks/MermaidDiagram/MermaidDiagram.tsx +266 -0
- package/src/blocks/MermaidDiagram/index.ts +1 -0
- package/src/blocks/MultiActionButtonGroup/MultiActionButton.stories.tsx +118 -0
- package/src/blocks/MultiActionButtonGroup/MultiActionButton.tsx +40 -0
- package/src/blocks/MultiActionButtonGroup/MultiActionButtonGroup.tsx +16 -0
- package/src/blocks/MultiActionButtonGroup/index.ts +1 -0
- package/src/blocks/MultiCombobox/MultiCombobox.stories.tsx +324 -0
- package/src/blocks/MultiCombobox/MultiCombobox.tsx +241 -0
- package/src/blocks/MultiCombobox/index.ts +1 -0
- package/src/blocks/NumberInput/NumberInput.stories.tsx +112 -0
- package/src/blocks/NumberInput/NumberInput.test.tsx +148 -0
- package/src/blocks/NumberInput/NumberInput.tsx +89 -0
- package/src/blocks/NumberInput/index.ts +1 -0
- package/src/blocks/PageHeader/AGENTS.md +77 -0
- package/src/blocks/PageHeader/PageHeader.stories.tsx +272 -0
- package/src/blocks/PageHeader/PageHeader.tsx +7 -0
- package/src/blocks/PageHeader/PageHeaderDetail.tsx +62 -0
- package/src/blocks/PageHeader/PageHeaderIndex.tsx +56 -0
- package/src/blocks/PageHeader/index.ts +1 -0
- package/src/blocks/PageHeader/styles.ts +6 -0
- package/src/blocks/PageLayout/AGENTS.md +94 -0
- package/src/blocks/PageLayout/PageLayout.stories.tsx +143 -0
- package/src/blocks/PageLayout/PageLayout.tsx +130 -0
- package/src/blocks/PageLayout/index.ts +1 -0
- package/src/blocks/PasswordInput/PasswordInput.stories.tsx +114 -0
- package/src/blocks/PasswordInput/PasswordInput.test.tsx +219 -0
- package/src/blocks/PasswordInput/PasswordInput.tsx +180 -0
- package/src/blocks/PasswordInput/index.ts +1 -0
- package/src/blocks/SearchInput/SearchInput.stories.tsx +127 -0
- package/src/blocks/SearchInput/SearchInput.test.tsx +162 -0
- package/src/blocks/SearchInput/SearchInput.tsx +146 -0
- package/src/blocks/SearchInput/index.ts +1 -0
- package/src/blocks/SearchInputWithNavigation/SearchInputWithNavigation.stories.tsx +73 -0
- package/src/blocks/SearchInputWithNavigation/SearchInputWithNavigation.tsx +89 -0
- package/src/blocks/SearchInputWithNavigation/index.ts +1 -0
- package/src/blocks/SplitButton/SplitButton.stories.tsx +88 -0
- package/src/blocks/SplitButton/SplitButton.tsx +68 -0
- package/src/blocks/SplitButton/index.ts +1 -0
- package/src/blocks/Tagger/TagItem.tsx +29 -0
- package/src/blocks/Tagger/TagSelectPopover/TagSelectCreateItem.tsx +42 -0
- package/src/blocks/Tagger/TagSelectPopover/TagSelectItem.tsx +31 -0
- package/src/blocks/Tagger/TagSelectPopover/TagSelectList.tsx +67 -0
- package/src/blocks/Tagger/TagSelectPopover/TagSelectPopover.tsx +34 -0
- package/src/blocks/Tagger/TagSelectPopover/TagSelectValue.tsx +47 -0
- package/src/blocks/Tagger/TagSelectPopover/index.ts +1 -0
- package/src/blocks/Tagger/Tagger.stories.tsx +80 -0
- package/src/blocks/Tagger/Tagger.tsx +101 -0
- package/src/blocks/Tagger/index.ts +1 -0
- package/src/blocks/Tagger/types.ts +11 -0
- package/src/blocks/Textarea/Textarea.stories.tsx +47 -0
- package/src/blocks/Textarea/Textarea.tsx +50 -0
- package/src/blocks/Textarea/index.ts +1 -0
- package/src/components/Alert/Alert.stories.tsx +93 -0
- package/src/components/Alert/Alert.tsx +103 -0
- package/src/components/Alert/AlertAction.tsx +31 -0
- package/src/components/Alert/AlertCollapsibleIcon.tsx +26 -0
- package/src/components/Alert/AlertDescription.tsx +31 -0
- package/src/components/Alert/AlertIcon.tsx +43 -0
- package/src/components/Alert/AlertRoot.tsx +64 -0
- package/src/components/Alert/AlertTitle.tsx +25 -0
- package/src/components/Alert/index.ts +1 -0
- package/src/components/Avatar.stories.tsx +22 -0
- package/src/components/Avatar.tsx +88 -0
- package/src/components/Badge/Badge.stories.tsx +106 -0
- package/src/components/Badge/Badge.tsx +55 -0
- package/src/components/Badge/BadgeAddon.tsx +63 -0
- package/src/components/Badge/BadgeButton.tsx +39 -0
- package/src/components/Badge/BadgeLabel.tsx +37 -0
- package/src/components/Badge/BadgeRoot.tsx +21 -0
- package/src/components/Badge/index.ts +1 -0
- package/src/components/Badge/styles.ts +74 -0
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +135 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +23 -0
- package/src/components/Breadcrumb/BreadcrumbEllipsis.tsx +43 -0
- package/src/components/Breadcrumb/BreadcrumbItem.tsx +9 -0
- package/src/components/Breadcrumb/BreadcrumbLink.tsx +48 -0
- package/src/components/Breadcrumb/BreadcrumbList.tsx +26 -0
- package/src/components/Breadcrumb/BreadcrumbPage.tsx +22 -0
- package/src/components/Breadcrumb/BreadcrumbSeparator.tsx +28 -0
- package/src/components/Breadcrumb/index.ts +1 -0
- package/src/components/Button/Button.stories.tsx +68 -0
- package/src/components/Button/Button.tsx +73 -0
- package/src/components/Button/ButtonInline.stories.tsx +54 -0
- package/src/components/Button/ButtonInline.tsx +73 -0
- package/src/components/Button/index.ts +3 -0
- package/src/components/Button/styles.ts +292 -0
- package/src/components/ButtonGroup/ButtonGroup.stories.tsx +118 -0
- package/src/components/ButtonGroup/ButtonGroup.tsx +79 -0
- package/src/components/ButtonGroup/index.ts +1 -0
- package/src/components/Calendar/Calendar.stories.tsx +101 -0
- package/src/components/Calendar/Calendar.tsx +44 -0
- package/src/components/Calendar/index.ts +2 -0
- package/src/components/Card/Card.stories.tsx +60 -0
- package/src/components/Card/Card.tsx +25 -0
- package/src/components/Card/CardContent.tsx +5 -0
- package/src/components/Card/CardHeader.tsx +5 -0
- package/src/components/Card/CardTitle.tsx +13 -0
- package/src/components/Card/index.ts +1 -0
- package/src/components/Card/styles.ts +32 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +210 -0
- package/src/components/Checkbox/Checkbox.test.tsx +140 -0
- package/src/components/Checkbox/Checkbox.tsx +86 -0
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/Checkbox/styles.ts +77 -0
- package/src/components/Clipboard/Clipboard.stories.tsx +44 -0
- package/src/components/Clipboard/Clipboard.tsx +89 -0
- package/src/components/Clipboard/index.ts +1 -0
- package/src/components/CodeEditor/ButtonToolbar.tsx +14 -0
- package/src/components/CodeEditor/CodeEditor.tsx +155 -0
- package/src/components/CodeEditor/SqlEditor.stories.tsx +76 -0
- package/src/components/CodeEditor/SqlEditor.tsx +206 -0
- package/src/components/CodeEditor/ToggleComponentHelp.tsx +7 -0
- package/src/components/CodeEditor/activeQueryHighlight.test.ts +178 -0
- package/src/components/CodeEditor/activeQueryHighlight.ts +245 -0
- package/src/components/CodeEditor/helpers.ts +100 -0
- package/src/components/CodeEditor/highlighting.ts +184 -0
- package/src/components/CodeEditor/index.ts +3 -0
- package/src/components/CodeEditor/search.ts +186 -0
- package/src/components/Collapse.stories.tsx +56 -0
- package/src/components/Collapse.tsx +61 -0
- package/src/components/Collapsible/Collapsible.stories.tsx +55 -0
- package/src/components/Collapsible/Collapsible.tsx +11 -0
- package/src/components/Collapsible/CollapsibleContent.tsx +25 -0
- package/src/components/Collapsible/index.ts +1 -0
- package/src/components/CollapsiblePanel.stories.tsx +25 -0
- package/src/components/CollapsiblePanel.tsx +130 -0
- package/src/components/Combobox/Combobox.stories.tsx +426 -0
- package/src/components/Combobox/Combobox.test.tsx +165 -0
- package/src/components/Combobox/Combobox.tsx +347 -0
- package/src/components/Combobox/ComboboxChips.tsx +63 -0
- package/src/components/Combobox/ComboboxContent.tsx +95 -0
- package/src/components/Combobox/ComboboxEmpty.tsx +30 -0
- package/src/components/Combobox/ComboboxInput.tsx +10 -0
- package/src/components/Combobox/ComboboxInputGroup.tsx +38 -0
- package/src/components/Combobox/ComboboxItem.tsx +71 -0
- package/src/components/Combobox/ComboboxItemIndicator.tsx +76 -0
- package/src/components/Combobox/ComboboxList.tsx +32 -0
- package/src/components/Combobox/ComboboxRoot.tsx +5 -0
- package/src/components/Combobox/ComboboxSeparator.tsx +20 -0
- package/src/components/Combobox/Group/ComboboxCollection.tsx +7 -0
- package/src/components/Combobox/Group/ComboboxGroup.tsx +23 -0
- package/src/components/Combobox/Group/ComboboxGroupLabel.tsx +38 -0
- package/src/components/Combobox/Group/index.ts +3 -0
- package/src/components/Combobox/index.ts +1 -0
- package/src/components/Combobox/types.ts +26 -0
- package/src/components/Combobox/utils.test.ts +95 -0
- package/src/components/Combobox/utils.ts +103 -0
- package/src/components/Command/Command.stories.tsx +376 -0
- package/src/components/Command/Command.tsx +37 -0
- package/src/components/Command/CommandContent.tsx +19 -0
- package/src/components/Command/CommandEmpty.tsx +6 -0
- package/src/components/Command/CommandGroup.tsx +26 -0
- package/src/components/Command/CommandInput.tsx +27 -0
- package/src/components/Command/CommandItem/CheckIndicator.tsx +74 -0
- package/src/components/Command/CommandItem/CommandItem.tsx +91 -0
- package/src/components/Command/CommandItem/index.ts +1 -0
- package/src/components/Command/CommandList.tsx +32 -0
- package/src/components/Command/CommandSeparator.tsx +13 -0
- package/src/components/Command/index.ts +6 -0
- package/src/components/DatePicker/DatePicker.tsx +37 -0
- package/src/components/DatePicker/index.ts +1 -0
- package/src/components/DiffView/DiffView.stories.tsx +80 -0
- package/src/components/DiffView/DiffView.tsx +131 -0
- package/src/components/DiffView/DiffViewMark.tsx +21 -0
- package/src/components/DiffView/DiffViewTd.tsx +84 -0
- package/src/components/DiffView/DiffViewTh.tsx +23 -0
- package/src/components/DiffView/index.ts +11 -0
- package/src/components/DiffView/util.test.ts +231 -0
- package/src/components/DiffView/util.ts +311 -0
- package/src/components/DotsLoader.tsx +22 -0
- package/src/components/DropdownMenu/DropdownMenu.stories.tsx +306 -0
- package/src/components/DropdownMenu/DropdownMenu.tsx +61 -0
- package/src/components/DropdownMenu/DropdownMenuCheckboxItem.tsx +52 -0
- package/src/components/DropdownMenu/DropdownMenuContent.tsx +52 -0
- package/src/components/DropdownMenu/DropdownMenuContext.tsx +13 -0
- package/src/components/DropdownMenu/DropdownMenuItem.tsx +74 -0
- package/src/components/DropdownMenu/DropdownMenuItemDescription.tsx +42 -0
- package/src/components/DropdownMenu/DropdownMenuItemIndicator.tsx +98 -0
- package/src/components/DropdownMenu/DropdownMenuLabel.tsx +21 -0
- package/src/components/DropdownMenu/DropdownMenuRadioGroup/DropdownMenuRadioGroup.tsx +31 -0
- package/src/components/DropdownMenu/DropdownMenuRadioGroup/DropdownMenuRadioItem.tsx +64 -0
- package/src/components/DropdownMenu/DropdownMenuRadioGroup/index.ts +2 -0
- package/src/components/DropdownMenu/DropdownMenuSeparator.tsx +35 -0
- package/src/components/DropdownMenu/DropdownMenuSub/DropdownMenuSub.tsx +3 -0
- package/src/components/DropdownMenu/DropdownMenuSub/DropdownMenuSubContent.tsx +21 -0
- package/src/components/DropdownMenu/DropdownMenuSub/DropdownMenuSubTrigger.tsx +17 -0
- package/src/components/DropdownMenu/DropdownMenuSub/index.ts +3 -0
- package/src/components/DropdownMenu/DropdownMenuTrigger.tsx +19 -0
- package/src/components/DropdownMenu/ItemIcon.tsx +22 -0
- package/src/components/DropdownMenu/index.ts +1 -0
- package/src/components/DropdownMenu/styles.ts +82 -0
- package/src/components/EmptySearchContent.stories.tsx +12 -0
- package/src/components/EmptySearchContent.tsx +13 -0
- package/src/components/FieldBox/FieldBox.stories.tsx +138 -0
- package/src/components/FieldBox/FieldBox.tsx +43 -0
- package/src/components/FieldBox/FieldBoxDescription.tsx +18 -0
- package/src/components/FieldBox/FieldBoxTitle.tsx +20 -0
- package/src/components/FieldBox/index.ts +1 -0
- package/src/components/FieldBox/styles.ts +37 -0
- package/src/components/FormGroup/FormGroup.stories.tsx +120 -0
- package/src/components/FormGroup/FormGroup.tsx +33 -0
- package/src/components/FormGroup/FormGroupColorInput.tsx +15 -0
- package/src/components/FormGroup/FormGroupHelp.tsx +17 -0
- package/src/components/FormGroup/FormGroupNumberInput.tsx +15 -0
- package/src/components/FormGroup/FormGroupTextInput.tsx +15 -0
- package/src/components/FormGroup/FormGroupTextarea.tsx +12 -0
- package/src/components/FormGroup/context.ts +11 -0
- package/src/components/FormGroup/index.ts +2 -0
- package/src/components/FormGroup/types.ts +5 -0
- package/src/components/FullScreenModal/FullScreenModal.tsx +8 -0
- package/src/components/FullScreenModal/FullScreenModalBody.tsx +16 -0
- package/src/components/FullScreenModal/FullScreenModalHeader.tsx +89 -0
- package/src/components/FullScreenModal/FullScreenModalRoot.tsx +79 -0
- package/src/components/FullScreenModal/index.ts +3 -0
- package/src/components/HelpBlock/HelpBlock.stories.tsx +28 -0
- package/src/components/HelpBlock/HelpBlock.tsx +31 -0
- package/src/components/HelpBlock/index.ts +1 -0
- package/src/components/HoverCard/HoverCard.stories.tsx +77 -0
- package/src/components/HoverCard/HoverCard.tsx +13 -0
- package/src/components/HoverCard/HoverCardContent.tsx +63 -0
- package/src/components/HoverCard/index.ts +1 -0
- package/src/components/Icon/Icon.stories.tsx +26 -0
- package/src/components/Icon/Icon.tsx +10 -0
- package/src/components/Icon/Icons.mdx +28 -0
- package/src/components/Icon/constants.ts +1 -0
- package/src/components/Icon/index.ts +27 -0
- package/src/components/Input/DateInput/DateInput.stories.tsx +58 -0
- package/src/components/Input/DateInput/DateInput.tsx +140 -0
- package/src/components/Input/DateInput/helpers.test.ts +102 -0
- package/src/components/Input/DateInput/helpers.ts +59 -0
- package/src/components/Input/DateInput/index.ts +1 -0
- package/src/components/Input/DateRangeInput/DateRangeInput.stories.tsx +66 -0
- package/src/components/Input/DateRangeInput/DateRangeInput.tsx +141 -0
- package/src/components/Input/DateRangeInput/helpers.test.ts +146 -0
- package/src/components/Input/DateRangeInput/helpers.ts +36 -0
- package/src/components/Input/DateRangeInput/index.ts +1 -0
- package/src/components/Input/Input.stories.tsx +93 -0
- package/src/components/Input/Input.test.tsx +112 -0
- package/src/components/Input/Input.tsx +147 -0
- package/src/components/Input/TextInput/TextInput.stories.tsx +68 -0
- package/src/components/Input/TextInput/TextInput.test.tsx +52 -0
- package/src/components/Input/TextInput/TextInput.tsx +57 -0
- package/src/components/Input/TextInput/index.ts +1 -0
- package/src/components/Input/TimeInput/TimeInput.stories.tsx +89 -0
- package/src/components/Input/TimeInput/TimeInput.tsx +103 -0
- package/src/components/Input/TimeInput/index.ts +1 -0
- package/src/components/Input/index.ts +4 -0
- package/src/components/Input/utils.ts +11 -0
- package/src/components/InputGroup/InputGroup.stories.tsx +208 -0
- package/src/components/InputGroup/InputGroup.tsx +228 -0
- package/src/components/InputGroup/InputGroupAddon.tsx +46 -0
- package/src/components/InputGroup/InputGroupButton.tsx +25 -0
- package/src/components/InputGroup/InputGroupInput.tsx +34 -0
- package/src/components/InputGroup/InputGroupText.tsx +21 -0
- package/src/components/InputGroup/InputGroupTextArea.tsx +78 -0
- package/src/components/InputGroup/index.ts +1 -0
- package/src/components/Kbd/Kbd.stories.tsx +82 -0
- package/src/components/Kbd/Kbd.tsx +17 -0
- package/src/components/Kbd/KbdGroup.tsx +35 -0
- package/src/components/Kbd/KbdSeparator.tsx +9 -0
- package/src/components/Kbd/index.ts +3 -0
- package/src/components/Kbd/keyboardKeys.ts +40 -0
- package/src/components/Kbd/styles.ts +39 -0
- package/src/components/Label/Label.stories.tsx +53 -0
- package/src/components/Label/Label.tsx +31 -0
- package/src/components/Label/index.ts +1 -0
- package/src/components/Link.stories.tsx +19 -0
- package/src/components/Link.tsx +37 -0
- package/src/components/MarkdownEditor/MarkdownEditor.stories.tsx +120 -0
- package/src/components/MarkdownEditor/MarkdownEditor.test.ts +171 -0
- package/src/components/MarkdownEditor/MarkdownEditor.tsx +155 -0
- package/src/components/MarkdownEditor/context.ts +10 -0
- package/src/components/MarkdownEditor/index.ts +1 -0
- package/src/components/MarkdownEditor/nodes/ImageNode.tsx +115 -0
- package/src/components/MarkdownEditor/nodes/ImageNodeComponent.tsx +18 -0
- package/src/components/MarkdownEditor/nodes/MermaidNode.tsx +72 -0
- package/src/components/MarkdownEditor/nodes/MermaidNodeComponent.tsx +35 -0
- package/src/components/MarkdownEditor/plugins/ImagePastePlugin.tsx +149 -0
- package/src/components/MarkdownEditor/plugins/KeyboardPlugin.tsx +37 -0
- package/src/components/MarkdownEditor/plugins/MarkdownSyncPlugin.tsx +56 -0
- package/src/components/MarkdownEditor/plugins/ToolbarPlugin.tsx +274 -0
- package/src/components/MarkdownEditor/plugins/toolbar/AlignmentDropdown.tsx +93 -0
- package/src/components/MarkdownEditor/plugins/toolbar/BlockTypeDropdown.tsx +133 -0
- package/src/components/MarkdownEditor/plugins/toolbar/LinkPanel.tsx +160 -0
- package/src/components/MarkdownEditor/plugins/toolbar/ToolbarButton.tsx +36 -0
- package/src/components/MarkdownEditor/plugins/toolbar/ToolbarDivider.tsx +1 -0
- package/src/components/MarkdownEditor/theme.ts +39 -0
- package/src/components/MarkdownEditor/transformers.ts +179 -0
- package/src/components/MarkedText/MarkedText.stories.tsx +46 -0
- package/src/components/MarkedText/MarkedText.tsx +36 -0
- package/src/components/MarkedText/index.ts +1 -0
- package/src/components/MarkedText/utils.test.ts +193 -0
- package/src/components/MarkedText/utils.ts +37 -0
- package/src/components/MergeEditor/AcceptControls/AcceptButton.tsx +20 -0
- package/src/components/MergeEditor/AcceptControls/AcceptControls.tsx +66 -0
- package/src/components/MergeEditor/ConflictStatus/ConflictStatusIndicator.tsx +38 -0
- package/src/components/MergeEditor/MergeEditor.stories.tsx +211 -0
- package/src/components/MergeEditor/MergeEditor.tsx +116 -0
- package/src/components/MergeEditor/MergePane.tsx +95 -0
- package/src/components/MergeEditor/PaneHeader.tsx +11 -0
- package/src/components/MergeEditor/constants.ts +48 -0
- package/src/components/MergeEditor/decorations.ts +126 -0
- package/src/components/MergeEditor/helpers.test.ts +172 -0
- package/src/components/MergeEditor/helpers.ts +118 -0
- package/src/components/MergeEditor/index.ts +5 -0
- package/src/components/MergeEditor/useMergeDiff.ts +573 -0
- package/src/components/MergeEditor/useScrollSync.ts +79 -0
- package/src/components/Modal/Modal.stories.tsx +678 -0
- package/src/components/Modal/Modal.tsx +23 -0
- package/src/components/Modal/ModalBody.tsx +23 -0
- package/src/components/Modal/ModalCloseIcon.tsx +32 -0
- package/src/components/Modal/ModalContent.tsx +87 -0
- package/src/components/Modal/ModalDescription.tsx +20 -0
- package/src/components/Modal/ModalFooter.tsx +15 -0
- package/src/components/Modal/ModalHeader.tsx +50 -0
- package/src/components/Modal/ModalIcon.tsx +83 -0
- package/src/components/Modal/ModalOverlay.tsx +42 -0
- package/src/components/Modal/ModalTitle.tsx +12 -0
- package/src/components/Modal/index.ts +1 -0
- package/src/components/Navbar/Navbar.stories.tsx +179 -0
- package/src/components/Navbar/Navbar.tsx +80 -0
- package/src/components/Navbar/NavbarContent.tsx +20 -0
- package/src/components/Navbar/NavbarFooter.tsx +11 -0
- package/src/components/Navbar/NavbarHeader.tsx +11 -0
- package/src/components/Navbar/NavbarMenu.tsx +30 -0
- package/src/components/Navbar/NavbarMenuAvatar.tsx +67 -0
- package/src/components/Navbar/NavbarMenuButton.tsx +134 -0
- package/src/components/Navbar/NavbarMenuItem.tsx +9 -0
- package/src/components/Navbar/NavbarMenuItemAdornment.tsx +65 -0
- package/src/components/Navbar/index.ts +1 -0
- package/src/components/Pagination/Pagination.stories.tsx +132 -0
- package/src/components/Pagination/Pagination.tsx +34 -0
- package/src/components/Pagination/PaginationButton.tsx +36 -0
- package/src/components/Pagination/PaginationContent.tsx +12 -0
- package/src/components/Pagination/PaginationEllipsis.tsx +26 -0
- package/src/components/Pagination/PaginationItem.tsx +9 -0
- package/src/components/Pagination/PaginationNext.tsx +19 -0
- package/src/components/Pagination/PaginationPrevious.tsx +11 -0
- package/src/components/Pagination/index.ts +1 -0
- package/src/components/PanelWithDetails.stories.tsx +28 -0
- package/src/components/PanelWithDetails.tsx +66 -0
- package/src/components/Popconfirm/Popconfirm.stories.tsx +365 -0
- package/src/components/Popconfirm/Popconfirm.tsx +82 -0
- package/src/components/Popconfirm/PopconfirmBody.tsx +5 -0
- package/src/components/Popconfirm/PopconfirmCancel.tsx +45 -0
- package/src/components/Popconfirm/PopconfirmConfirm.tsx +57 -0
- package/src/components/Popconfirm/PopconfirmContent.tsx +33 -0
- package/src/components/Popconfirm/PopconfirmFooter.tsx +9 -0
- package/src/components/Popconfirm/PopconfirmTrigger.tsx +22 -0
- package/src/components/Popconfirm/context.ts +19 -0
- package/src/components/Popconfirm/index.ts +1 -0
- package/src/components/Popover/Popover.stories.tsx +85 -0
- package/src/components/Popover/Popover.tsx +15 -0
- package/src/components/Popover/PopoverContent.tsx +99 -0
- package/src/components/Popover/index.ts +1 -0
- package/src/components/Progress/Progress.stories.tsx +67 -0
- package/src/components/Progress/Progress.tsx +45 -0
- package/src/components/Progress/index.ts +1 -0
- package/src/components/RadioGroup/Radio.tsx +67 -0
- package/src/components/RadioGroup/RadioGroup.stories.tsx +300 -0
- package/src/components/RadioGroup/RadioGroup.tsx +64 -0
- package/src/components/RadioGroup/RadioGroupItem.tsx +60 -0
- package/src/components/RadioGroup/RadioGroupItemButton.tsx +79 -0
- package/src/components/RadioGroup/RadioGroupSeparator.tsx +12 -0
- package/src/components/RadioGroup/context.ts +13 -0
- package/src/components/RadioGroup/index.ts +1 -0
- package/src/components/Resizable/Resizable.stories.tsx +50 -0
- package/src/components/Resizable/ResizableHandle.tsx +50 -0
- package/src/components/Resizable/ResizablePanel.tsx +12 -0
- package/src/components/Resizable/ResizablePanelGroup.tsx +19 -0
- package/src/components/Resizable/index.ts +3 -0
- package/src/components/ScrollArea/ScrollArea.stories.tsx +43 -0
- package/src/components/ScrollArea/ScrollArea.tsx +50 -0
- package/src/components/ScrollArea/ScrollBar.tsx +32 -0
- package/src/components/ScrollArea/index.ts +2 -0
- package/src/components/Search/ClearButton.tsx +42 -0
- package/src/components/Search/Search.stories.tsx +77 -0
- package/src/components/Search/Search.test.tsx +224 -0
- package/src/components/Search/Search.tsx +139 -0
- package/src/components/Search/SearchForm.tsx +37 -0
- package/src/components/Search/index.ts +2 -0
- package/src/components/Search/styles.ts +88 -0
- package/src/components/Search/utils.ts +11 -0
- package/src/components/Select/Select.stories.tsx +109 -0
- package/src/components/Select/Select.tsx +130 -0
- package/src/components/Select/SelectContent.tsx +101 -0
- package/src/components/Select/SelectGroup.tsx +10 -0
- package/src/components/Select/SelectItem.tsx +71 -0
- package/src/components/Select/SelectLabel.tsx +32 -0
- package/src/components/Select/SelectRoot.tsx +6 -0
- package/src/components/Select/SelectSeparator.tsx +13 -0
- package/src/components/Select/SelectTrigger.tsx +161 -0
- package/src/components/Select/index.ts +1 -0
- package/src/components/Separator/Separator.stories.tsx +41 -0
- package/src/components/Separator/Separator.tsx +84 -0
- package/src/components/Separator/index.ts +1 -0
- package/src/components/Sheet/Sheet.stories.tsx +63 -0
- package/src/components/Sheet/Sheet.tsx +21 -0
- package/src/components/Sheet/SheetBody.tsx +12 -0
- package/src/components/Sheet/SheetContent.tsx +131 -0
- package/src/components/Sheet/SheetFooter.tsx +9 -0
- package/src/components/Sheet/SheetHeader.tsx +39 -0
- package/src/components/Sheet/SheetOverlay.tsx +11 -0
- package/src/components/Sheet/SheetTitle.tsx +12 -0
- package/src/components/Sheet/index.ts +1 -0
- package/src/components/Sidebar/Sidebar.stories.tsx +812 -0
- package/src/components/Sidebar/Sidebar.tsx +225 -0
- package/src/components/Sidebar/SidebarContent.tsx +61 -0
- package/src/components/Sidebar/SidebarFillTrigger.tsx +25 -0
- package/src/components/Sidebar/SidebarFooter.tsx +13 -0
- package/src/components/Sidebar/SidebarGroup/SidebarGroup.tsx +23 -0
- package/src/components/Sidebar/SidebarGroup/SidebarGroupAction.tsx +49 -0
- package/src/components/Sidebar/SidebarGroup/SidebarGroupContent.tsx +20 -0
- package/src/components/Sidebar/SidebarGroup/SidebarGroupLabel.tsx +76 -0
- package/src/components/Sidebar/SidebarGroup/index.ts +4 -0
- package/src/components/Sidebar/SidebarHeader.tsx +20 -0
- package/src/components/Sidebar/SidebarInstanceContext.ts +18 -0
- package/src/components/Sidebar/SidebarMenu/SidebarMenu.tsx +28 -0
- package/src/components/Sidebar/SidebarMenu/SidebarMenuAction.tsx +69 -0
- package/src/components/Sidebar/SidebarMenu/SidebarMenuBadge.tsx +56 -0
- package/src/components/Sidebar/SidebarMenu/SidebarMenuButton.tsx +108 -0
- package/src/components/Sidebar/SidebarMenu/SidebarMenuCard.tsx +107 -0
- package/src/components/Sidebar/SidebarMenu/SidebarMenuIcon.tsx +30 -0
- package/src/components/Sidebar/SidebarMenu/SidebarMenuItem.tsx +9 -0
- package/src/components/Sidebar/SidebarMenu/SidebarMenuSub.tsx +143 -0
- package/src/components/Sidebar/SidebarMenu/SidebarMenuSubItem.tsx +106 -0
- package/src/components/Sidebar/SidebarMenu/SidebarSearchInput.tsx +90 -0
- package/src/components/Sidebar/SidebarMenu/index.ts +10 -0
- package/src/components/Sidebar/SidebarProvider/SidebarProvider.tsx +171 -0
- package/src/components/Sidebar/SidebarProvider/context.ts +35 -0
- package/src/components/Sidebar/SidebarProvider/index.ts +2 -0
- package/src/components/Sidebar/SidebarProvider/useHoverTracking.test.ts +199 -0
- package/src/components/Sidebar/SidebarProvider/useHoverTracking.ts +72 -0
- package/src/components/Sidebar/SidebarProvider/useSidebarState.ts +50 -0
- package/src/components/Sidebar/SidebarProvider/useTransitionPresence.test.ts +179 -0
- package/src/components/Sidebar/SidebarProvider/useTransitionPresence.ts +53 -0
- package/src/components/Sidebar/SidebarResizeHandle.tsx +28 -0
- package/src/components/Sidebar/SidebarSeparator.tsx +15 -0
- package/src/components/Sidebar/SidebarTrigger.tsx +40 -0
- package/src/components/Sidebar/index.ts +3 -0
- package/src/components/Sidebar/types.ts +6 -0
- package/src/components/Sidebar/useResizeSidebar.ts +112 -0
- package/src/components/Sidebar/utils.test.ts +178 -0
- package/src/components/Sidebar/utils.ts +70 -0
- package/src/components/Skeleton/Skeleton.stories.tsx +54 -0
- package/src/components/Skeleton/Skeleton.tsx +54 -0
- package/src/components/Skeleton/SkeletonList.stories.tsx +11 -0
- package/src/components/Skeleton/SkeletonList.tsx +65 -0
- package/src/components/Slider/Slider.stories.tsx +99 -0
- package/src/components/Slider/Slider.tsx +85 -0
- package/src/components/Slider/SliderThumb.tsx +57 -0
- package/src/components/Slider/SliderTrack.tsx +49 -0
- package/src/components/Slider/index.ts +1 -0
- package/src/components/Slider/useInteracting.test.ts +159 -0
- package/src/components/Slider/useInteracting.ts +64 -0
- package/src/components/Switch/Switch.stories.tsx +177 -0
- package/src/components/Switch/Switch.tsx +58 -0
- package/src/components/Switch/index.ts +1 -0
- package/src/components/Switch/styles.ts +173 -0
- package/src/components/Table/Table.stories.tsx +106 -0
- package/src/components/Table/Table.tsx +51 -0
- package/src/components/Table/TableBody.tsx +22 -0
- package/src/components/Table/TableCell.tsx +7 -0
- package/src/components/Table/TableFooter.tsx +15 -0
- package/src/components/Table/TableHead.tsx +15 -0
- package/src/components/Table/TableHeader.tsx +20 -0
- package/src/components/Table/TableRow.tsx +11 -0
- package/src/components/Table/index.ts +1 -0
- package/src/components/Tabs.stories.tsx +123 -0
- package/src/components/Tabs.tsx +111 -0
- package/src/components/ThemeProvider/AIGradientSvg.tsx +18 -0
- package/src/components/ThemeProvider/ThemeProvider.tsx +25 -0
- package/src/components/ThemeProvider/index.ts +1 -0
- package/src/components/TimePicker/TimePicker.tsx +95 -0
- package/src/components/TimePicker/TimePickerButton.tsx +39 -0
- package/src/components/TimePicker/index.ts +1 -0
- package/src/components/TimePicker/utils.ts +31 -0
- package/src/components/Timeline/Timeline.stories.tsx +246 -0
- package/src/components/Timeline/Timeline.tsx +35 -0
- package/src/components/Timeline/TimelineConnector.tsx +154 -0
- package/src/components/Timeline/TimelineContent.tsx +18 -0
- package/src/components/Timeline/TimelineItem.tsx +14 -0
- package/src/components/Timeline/index.ts +1 -0
- package/src/components/Timeline/useLastLineHeight.ts +36 -0
- package/src/components/Toast/Toast.stories.tsx +178 -0
- package/src/components/Toast/ToastAction.tsx +40 -0
- package/src/components/Toast/ToastDescription.tsx +28 -0
- package/src/components/Toast/ToastDismiss.tsx +36 -0
- package/src/components/Toast/ToastDuration.tsx +43 -0
- package/src/components/Toast/ToastRoot.tsx +130 -0
- package/src/components/Toast/ToastTitle.tsx +33 -0
- package/src/components/Toast/Toaster.tsx +25 -0
- package/src/components/Toast/index.ts +3 -0
- package/src/components/Toast/toast.tsx +94 -0
- package/src/components/Toast/useNotification.tsx +33 -0
- package/src/components/Toggle/Toggle.stories.tsx +67 -0
- package/src/components/Toggle/Toggle.tsx +197 -0
- package/src/components/Toggle/index.ts +1 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +82 -0
- package/src/components/Tooltip/Tooltip.tsx +87 -0
- package/src/components/Tooltip/TooltipContent.tsx +90 -0
- package/src/components/Tooltip/TooltipRoot.tsx +12 -0
- package/src/components/Tooltip/TooltipTrigger.tsx +42 -0
- package/src/components/Tooltip/index.ts +1 -0
- package/src/components/Truncated.stories.tsx +72 -0
- package/src/components/Truncated.tsx +78 -0
- package/src/components/ZoomableSvg/ZoomableSvg.stories.tsx +53 -0
- package/src/components/ZoomableSvg/ZoomableSvg.tsx +198 -0
- package/src/components/ZoomableSvg/index.ts +1 -0
- package/src/constants/keyCodes.ts +5 -0
- package/src/docs/Colors.mdx +19 -0
- package/src/docs/Spacing.mdx +35 -0
- package/src/docs/Typography.mdx +63 -0
- package/src/docs/components/Typeset.tsx +35 -0
- package/src/hooks/useAutosizeInput.stories.tsx +142 -0
- package/src/hooks/useAutosizeInput.test.tsx +77 -0
- package/src/hooks/useAutosizeInput.tsx +107 -0
- package/src/hooks/useElement.ts +16 -0
- package/src/hooks/useScrollbarPosition.ts +68 -0
- package/src/icon-names.ts +20 -0
- package/src/index.ts +87 -0
- package/src/styles.css +212 -0
- package/src/utils/classNames.test.ts +16 -0
- package/src/utils/classNames.ts +27 -0
- package/src/utils/copyToClipboard.ts +3 -0
- package/src/utils/createChangeInputEvent.ts +28 -0
- package/src/utils/createPrefixedString.ts +2 -0
- package/src/utils/debounce.test.ts +63 -0
- package/src/utils/debounce.ts +17 -0
- package/src/utils/decorators.tsx +16 -0
- package/src/utils/escapeStringRegexp.ts +4 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/isMac.ts +26 -0
- package/src/utils/mergeRefs.ts +23 -0
- package/src/utils/noop.ts +1 -0
- package/src/utils/useSHA256.ts +21 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { PasswordInput } from './PasswordInput';
|
|
6
|
+
|
|
7
|
+
const renderInput = (ui: ReactElement) => render(ui);
|
|
8
|
+
|
|
9
|
+
const getInput = () => screen.getByPlaceholderText('Password') as HTMLInputElement;
|
|
10
|
+
const queryClearButton = () => screen.queryByLabelText('Clear password');
|
|
11
|
+
const queryShowButton = () => screen.queryByLabelText('Show password');
|
|
12
|
+
const queryHideButton = () => screen.queryByLabelText('Hide password');
|
|
13
|
+
const queryCapsLockIcon = () => screen.queryByTitle('Caps Lock is on');
|
|
14
|
+
|
|
15
|
+
describe('<PasswordInput />', () => {
|
|
16
|
+
describe('controlled state', () => {
|
|
17
|
+
it('renders empty when value is null', () => {
|
|
18
|
+
renderInput(<PasswordInput placeholder="Password" value={null} />);
|
|
19
|
+
expect(getInput().value).toBe('');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders empty when value is undefined and no defaultValue', () => {
|
|
23
|
+
renderInput(<PasswordInput placeholder="Password" />);
|
|
24
|
+
expect(getInput().value).toBe('');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders the controlled value', () => {
|
|
28
|
+
renderInput(<PasswordInput placeholder="Password" value="hunter2" onValueChange={vi.fn()} />);
|
|
29
|
+
expect(getInput().value).toBe('hunter2');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('emits raw onValueChange on every keystroke', () => {
|
|
33
|
+
const onValueChange = vi.fn();
|
|
34
|
+
renderInput(<PasswordInput placeholder="Password" value="" onValueChange={onValueChange} />);
|
|
35
|
+
fireEvent.change(getInput(), { target: { value: 'abc' } });
|
|
36
|
+
expect(onValueChange).toHaveBeenCalledWith('abc');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('emits the value verbatim via onValueBlur (no trimming)', () => {
|
|
40
|
+
const onValueBlur = vi.fn();
|
|
41
|
+
renderInput(
|
|
42
|
+
<PasswordInput placeholder="Password" value=" spaces " onValueBlur={onValueBlur} />,
|
|
43
|
+
);
|
|
44
|
+
fireEvent.blur(getInput());
|
|
45
|
+
expect(onValueBlur).toHaveBeenCalledWith(' spaces ');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('uncontrolled state', () => {
|
|
50
|
+
it('seeds the input from defaultValue', () => {
|
|
51
|
+
renderInput(<PasswordInput placeholder="Password" defaultValue="seed" />);
|
|
52
|
+
expect(getInput().value).toBe('seed');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('treats null defaultValue as empty', () => {
|
|
56
|
+
renderInput(<PasswordInput placeholder="Password" defaultValue={null} />);
|
|
57
|
+
expect(getInput().value).toBe('');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('keeps raw text in the DOM while typing', () => {
|
|
61
|
+
const onValueChange = vi.fn();
|
|
62
|
+
renderInput(<PasswordInput placeholder="Password" onValueChange={onValueChange} />);
|
|
63
|
+
const input = getInput();
|
|
64
|
+
fireEvent.change(input, { target: { value: 'p4ss' } });
|
|
65
|
+
expect(input.value).toBe('p4ss');
|
|
66
|
+
expect(onValueChange).toHaveBeenLastCalledWith('p4ss');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('clear button', () => {
|
|
71
|
+
it('is hidden when allowClear is false', () => {
|
|
72
|
+
renderInput(<PasswordInput placeholder="Password" defaultValue="abc" />);
|
|
73
|
+
expect(queryClearButton()).toBeNull();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('is hidden when there is no value', () => {
|
|
77
|
+
renderInput(<PasswordInput placeholder="Password" allowClear />);
|
|
78
|
+
expect(queryClearButton()).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('is hidden when disabled', () => {
|
|
82
|
+
renderInput(<PasswordInput placeholder="Password" allowClear disabled defaultValue="abc" />);
|
|
83
|
+
expect(queryClearButton()).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('is hidden when readOnly', () => {
|
|
87
|
+
renderInput(<PasswordInput placeholder="Password" allowClear readOnly defaultValue="abc" />);
|
|
88
|
+
expect(queryClearButton()).toBeNull();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('clears the DOM input in uncontrolled mode and emits onValueChange("")', () => {
|
|
92
|
+
const onValueChange = vi.fn();
|
|
93
|
+
renderInput(
|
|
94
|
+
<PasswordInput
|
|
95
|
+
placeholder="Password"
|
|
96
|
+
allowClear
|
|
97
|
+
defaultValue="abc"
|
|
98
|
+
onValueChange={onValueChange}
|
|
99
|
+
/>,
|
|
100
|
+
);
|
|
101
|
+
fireEvent.click(queryClearButton()!);
|
|
102
|
+
expect(getInput().value).toBe('');
|
|
103
|
+
expect(onValueChange).toHaveBeenCalledWith('');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('emits onValueChange("") in controlled mode', () => {
|
|
107
|
+
const onValueChange = vi.fn();
|
|
108
|
+
renderInput(
|
|
109
|
+
<PasswordInput
|
|
110
|
+
placeholder="Password"
|
|
111
|
+
allowClear
|
|
112
|
+
value="abc"
|
|
113
|
+
onValueChange={onValueChange}
|
|
114
|
+
/>,
|
|
115
|
+
);
|
|
116
|
+
fireEvent.click(queryClearButton()!);
|
|
117
|
+
expect(onValueChange).toHaveBeenCalledWith('');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('synthesizes an onChange event when clearing', () => {
|
|
121
|
+
const onChange = vi.fn();
|
|
122
|
+
renderInput(
|
|
123
|
+
<PasswordInput placeholder="Password" allowClear defaultValue="abc" onChange={onChange} />,
|
|
124
|
+
);
|
|
125
|
+
fireEvent.click(queryClearButton()!);
|
|
126
|
+
expect(onChange).toHaveBeenCalled();
|
|
127
|
+
expect(onChange.mock.lastCall?.[0].target.value).toBe('');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('password visibility toggle', () => {
|
|
132
|
+
it('does not render the toggle when allowPasswordToggle is false', () => {
|
|
133
|
+
renderInput(<PasswordInput placeholder="Password" />);
|
|
134
|
+
expect(queryShowButton()).toBeNull();
|
|
135
|
+
expect(queryHideButton()).toBeNull();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('starts hidden by default and toggles to visible', () => {
|
|
139
|
+
renderInput(<PasswordInput placeholder="Password" allowPasswordToggle />);
|
|
140
|
+
const input = getInput();
|
|
141
|
+
expect(input.type).toBe('password');
|
|
142
|
+
fireEvent.click(queryShowButton()!);
|
|
143
|
+
expect(input.type).toBe('text');
|
|
144
|
+
fireEvent.click(queryHideButton()!);
|
|
145
|
+
expect(input.type).toBe('password');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('honors defaultPasswordVisibility', () => {
|
|
149
|
+
renderInput(
|
|
150
|
+
<PasswordInput placeholder="Password" allowPasswordToggle defaultPasswordVisibility />,
|
|
151
|
+
);
|
|
152
|
+
expect(getInput().type).toBe('text');
|
|
153
|
+
expect(queryHideButton()).not.toBeNull();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('Caps Lock indicator', () => {
|
|
158
|
+
// jsdom's getModifierState always returns false; mock it on both KeyboardEvent
|
|
159
|
+
// and MouseEvent (separate prototype methods). Call again to flip state.
|
|
160
|
+
const mockCapsLock = (on: boolean) => {
|
|
161
|
+
const impl = (key: string) => key === 'CapsLock' && on;
|
|
162
|
+
vi.spyOn(KeyboardEvent.prototype, 'getModifierState').mockImplementation(impl);
|
|
163
|
+
vi.spyOn(MouseEvent.prototype, 'getModifierState').mockImplementation(impl);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
afterEach(() => {
|
|
167
|
+
vi.restoreAllMocks();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('is not rendered by default', () => {
|
|
171
|
+
renderInput(<PasswordInput placeholder="Password" />);
|
|
172
|
+
expect(queryCapsLockIcon()).toBeNull();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('does not appear when Caps Lock is off and a key is pressed', () => {
|
|
176
|
+
mockCapsLock(false);
|
|
177
|
+
renderInput(<PasswordInput placeholder="Password" />);
|
|
178
|
+
fireEvent.keyDown(getInput(), { key: 'a' });
|
|
179
|
+
expect(queryCapsLockIcon()).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('appears when Caps Lock is on and a key is pressed', () => {
|
|
183
|
+
mockCapsLock(true);
|
|
184
|
+
renderInput(<PasswordInput placeholder="Password" />);
|
|
185
|
+
fireEvent.keyDown(getInput(), { key: 'A' });
|
|
186
|
+
expect(queryCapsLockIcon()).not.toBeNull();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('disappears immediately when Caps Lock is toggled off via keyUp', () => {
|
|
190
|
+
mockCapsLock(true);
|
|
191
|
+
renderInput(<PasswordInput placeholder="Password" />);
|
|
192
|
+
const input = getInput();
|
|
193
|
+
fireEvent.keyDown(input, { key: 'A' });
|
|
194
|
+
expect(queryCapsLockIcon()).not.toBeNull();
|
|
195
|
+
|
|
196
|
+
// User presses Caps Lock to turn it off — getModifierState now returns false on keyUp.
|
|
197
|
+
mockCapsLock(false);
|
|
198
|
+
fireEvent.keyUp(input, { key: 'CapsLock' });
|
|
199
|
+
expect(queryCapsLockIcon()).toBeNull();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('appears on mouseDown when clicking into the field with Caps Lock on', () => {
|
|
203
|
+
mockCapsLock(true);
|
|
204
|
+
renderInput(<PasswordInput placeholder="Password" />);
|
|
205
|
+
fireEvent.mouseDown(getInput());
|
|
206
|
+
expect(queryCapsLockIcon()).not.toBeNull();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('disappears on blur', () => {
|
|
210
|
+
mockCapsLock(true);
|
|
211
|
+
renderInput(<PasswordInput placeholder="Password" />);
|
|
212
|
+
const input = getInput();
|
|
213
|
+
fireEvent.keyDown(input, { key: 'A' });
|
|
214
|
+
expect(queryCapsLockIcon()).not.toBeNull();
|
|
215
|
+
fireEvent.blur(input);
|
|
216
|
+
expect(queryCapsLockIcon()).toBeNull();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
ChangeEventHandler,
|
|
4
|
+
ComponentProps,
|
|
5
|
+
ElementRef,
|
|
6
|
+
FocusEventHandler,
|
|
7
|
+
KeyboardEventHandler,
|
|
8
|
+
MouseEventHandler,
|
|
9
|
+
Ref,
|
|
10
|
+
} from 'react';
|
|
11
|
+
|
|
12
|
+
import { Icon } from '../../components/Icon';
|
|
13
|
+
import { InputGroup } from '../../components/InputGroup';
|
|
14
|
+
import { createChangeInputEvent, mergeRefs } from '../../utils';
|
|
15
|
+
|
|
16
|
+
type InputGroupProps = Pick<
|
|
17
|
+
ComponentProps<typeof InputGroup>,
|
|
18
|
+
'variant' | 'focusVariant' | 'state' | 'sizing'
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
type InputGroupInputProps = Omit<
|
|
22
|
+
ComponentProps<typeof InputGroup.Input>,
|
|
23
|
+
'value' | 'defaultValue' | 'type' | 'autoComplete'
|
|
24
|
+
>;
|
|
25
|
+
|
|
26
|
+
type Props = InputGroupProps &
|
|
27
|
+
InputGroupInputProps & {
|
|
28
|
+
value?: string | null;
|
|
29
|
+
defaultValue?: string | null;
|
|
30
|
+
onValueChange?: (value: string) => void;
|
|
31
|
+
onValueBlur?: (value: string) => void;
|
|
32
|
+
allowClear?: boolean;
|
|
33
|
+
allowPasswordToggle?: boolean;
|
|
34
|
+
autoComplete?: 'new-password' | 'current-password' | 'off';
|
|
35
|
+
defaultPasswordVisibility?: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const PasswordInput = ({
|
|
39
|
+
ref,
|
|
40
|
+
|
|
41
|
+
// group props
|
|
42
|
+
state,
|
|
43
|
+
variant,
|
|
44
|
+
focusVariant,
|
|
45
|
+
sizing,
|
|
46
|
+
className,
|
|
47
|
+
|
|
48
|
+
// own props
|
|
49
|
+
value: valueProp,
|
|
50
|
+
defaultValue,
|
|
51
|
+
onValueChange,
|
|
52
|
+
onValueBlur,
|
|
53
|
+
onChange,
|
|
54
|
+
onBlur,
|
|
55
|
+
onKeyDown,
|
|
56
|
+
onKeyUp,
|
|
57
|
+
onMouseDown,
|
|
58
|
+
allowClear = false,
|
|
59
|
+
allowPasswordToggle = false,
|
|
60
|
+
autoComplete = 'new-password',
|
|
61
|
+
defaultPasswordVisibility = false,
|
|
62
|
+
|
|
63
|
+
// input props
|
|
64
|
+
disabled,
|
|
65
|
+
readOnly,
|
|
66
|
+
...restInputProps
|
|
67
|
+
}: Props & { ref?: Ref<ElementRef<'input'>> }) => {
|
|
68
|
+
const inputRef = useRef<ElementRef<'input'>>(null);
|
|
69
|
+
|
|
70
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? '');
|
|
71
|
+
const isControlled = valueProp !== undefined;
|
|
72
|
+
const value = isControlled ? (valueProp ?? '') : internalValue;
|
|
73
|
+
const setValue = (next: string) => {
|
|
74
|
+
if (!isControlled) setInternalValue(next);
|
|
75
|
+
onValueChange?.(next);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const [capsLock, setCapsLock] = useState(false);
|
|
79
|
+
const [isPasswordVisible, setIsPasswordVisible] = useState(defaultPasswordVisibility);
|
|
80
|
+
|
|
81
|
+
const canClear = allowClear && !!value && !disabled && !readOnly;
|
|
82
|
+
|
|
83
|
+
const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {
|
|
84
|
+
setValue(event.target.value);
|
|
85
|
+
onChange?.(event);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handleBlur: FocusEventHandler<HTMLInputElement> = (event) => {
|
|
89
|
+
if (capsLock) setCapsLock(false);
|
|
90
|
+
onValueBlur?.(event.target.value);
|
|
91
|
+
onBlur?.(event);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Both keyDown and keyUp are needed: pressing the Caps Lock key itself
|
|
95
|
+
// reports the *old* state on keyDown and the *new* state on keyUp, so
|
|
96
|
+
// toggling Caps Lock off mid-typing is only caught on keyUp.
|
|
97
|
+
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
|
|
98
|
+
const isOn = event.getModifierState('CapsLock');
|
|
99
|
+
if (isOn !== capsLock) setCapsLock(isOn);
|
|
100
|
+
onKeyDown?.(event);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const handleKeyUp: KeyboardEventHandler<HTMLInputElement> = (event) => {
|
|
104
|
+
const isOn = event.getModifierState('CapsLock');
|
|
105
|
+
if (isOn !== capsLock) setCapsLock(isOn);
|
|
106
|
+
onKeyUp?.(event);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Catches the case where the user clicks into the field with Caps Lock
|
|
110
|
+
// already on. Tab-focus / autofocus have no API to query modifier state.
|
|
111
|
+
const handleMouseDown: MouseEventHandler<HTMLInputElement> = (event) => {
|
|
112
|
+
const isOn = event.getModifierState('CapsLock');
|
|
113
|
+
if (isOn !== capsLock) setCapsLock(isOn);
|
|
114
|
+
onMouseDown?.(event);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleClear: MouseEventHandler<HTMLButtonElement> = () => {
|
|
118
|
+
if (!inputRef.current) return;
|
|
119
|
+
inputRef.current.value = '';
|
|
120
|
+
setValue('');
|
|
121
|
+
onChange?.(createChangeInputEvent(inputRef.current));
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const handleTogglePasswordVisibility = () => {
|
|
125
|
+
setIsPasswordVisible((prev) => !prev);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<InputGroup
|
|
130
|
+
state={state}
|
|
131
|
+
variant={variant}
|
|
132
|
+
sizing={sizing}
|
|
133
|
+
focusVariant={focusVariant}
|
|
134
|
+
className={className}
|
|
135
|
+
>
|
|
136
|
+
<InputGroup.Input
|
|
137
|
+
{...restInputProps}
|
|
138
|
+
ref={mergeRefs(ref ?? null, inputRef)}
|
|
139
|
+
type={isPasswordVisible ? 'text' : 'password'}
|
|
140
|
+
autoComplete={autoComplete}
|
|
141
|
+
value={value}
|
|
142
|
+
onChange={handleChange}
|
|
143
|
+
onBlur={handleBlur}
|
|
144
|
+
onKeyDown={handleKeyDown}
|
|
145
|
+
onKeyUp={handleKeyUp}
|
|
146
|
+
onMouseDown={handleMouseDown}
|
|
147
|
+
disabled={disabled}
|
|
148
|
+
readOnly={readOnly}
|
|
149
|
+
/>
|
|
150
|
+
|
|
151
|
+
{(capsLock || canClear || allowPasswordToggle) && (
|
|
152
|
+
<InputGroup.Addon align="inline-end">
|
|
153
|
+
{capsLock && (
|
|
154
|
+
<Icon
|
|
155
|
+
icon="up-from-line"
|
|
156
|
+
title="Caps Lock is on"
|
|
157
|
+
className="tw-size-4 tw-text-primary"
|
|
158
|
+
/>
|
|
159
|
+
)}
|
|
160
|
+
{canClear && (
|
|
161
|
+
<InputGroup.Button
|
|
162
|
+
icon="xmark"
|
|
163
|
+
aria-label="Clear password"
|
|
164
|
+
onMouseDown={(event) => event.preventDefault()}
|
|
165
|
+
onClick={handleClear}
|
|
166
|
+
/>
|
|
167
|
+
)}
|
|
168
|
+
{allowPasswordToggle && (
|
|
169
|
+
<InputGroup.Button
|
|
170
|
+
icon={isPasswordVisible ? 'eye-slash' : 'eye'}
|
|
171
|
+
aria-label={isPasswordVisible ? 'Hide password' : 'Show password'}
|
|
172
|
+
onMouseDown={(event) => event.preventDefault()}
|
|
173
|
+
onClick={handleTogglePasswordVisibility}
|
|
174
|
+
/>
|
|
175
|
+
)}
|
|
176
|
+
</InputGroup.Addon>
|
|
177
|
+
)}
|
|
178
|
+
</InputGroup>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PasswordInput';
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { ComponentProps } from 'react';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
|
+
|
|
6
|
+
import { SearchInput } from './SearchInput';
|
|
7
|
+
|
|
8
|
+
type Story = StoryObj<typeof SearchInput>;
|
|
9
|
+
type SearchInputProps = ComponentProps<typeof SearchInput>;
|
|
10
|
+
type State = NonNullable<SearchInputProps['state']>;
|
|
11
|
+
type Variant = NonNullable<SearchInputProps['variant']>;
|
|
12
|
+
type Sizing = NonNullable<SearchInputProps['sizing']>;
|
|
13
|
+
type FocusVariant = NonNullable<SearchInputProps['focusVariant']>;
|
|
14
|
+
|
|
15
|
+
const states: State[] = ['default', 'error', 'warning'];
|
|
16
|
+
const variants: Variant[] = ['primary', 'secondary', 'argo'];
|
|
17
|
+
const sizings: Sizing[] = ['small', 'default', 'medium'];
|
|
18
|
+
const focusVariants: FocusVariant[] = ['default', 'on', 'off'];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A debounced search input built on `InputGroup`.
|
|
22
|
+
*
|
|
23
|
+
* ### Key features
|
|
24
|
+
*
|
|
25
|
+
* **Debounced `onValueChange`** — by default the callback fires 300 ms after
|
|
26
|
+
* the user stops typing, preventing excessive re-renders or network requests
|
|
27
|
+
* on every keystroke. Tune via `debounceDelay` or disable entirely with
|
|
28
|
+
* `disableDebounce`.
|
|
29
|
+
*
|
|
30
|
+
* **Search icon prefix** — a magnifying-glass icon is always rendered as the
|
|
31
|
+
* inline-start prefix. It is part of the component and cannot be removed;
|
|
32
|
+
* that is by design — if you need a generic text input without the icon, use
|
|
33
|
+
* `TextInput` instead.
|
|
34
|
+
*
|
|
35
|
+
* **Optional clear button** — pass `allowClear` to show an ✕ button whenever
|
|
36
|
+
* the field has a value. Clicking it fires `onValueChange('')` immediately
|
|
37
|
+
* (not debounced) and calls the optional `onClear` callback.
|
|
38
|
+
*
|
|
39
|
+
* ### Controlled vs uncontrolled
|
|
40
|
+
*
|
|
41
|
+
* Works in both modes. In controlled mode the internal display value updates
|
|
42
|
+
* on every keystroke while the debounced `onValueChange` fires after the
|
|
43
|
+
* delay, so the input never feels laggy even when the parent re-renders only
|
|
44
|
+
* after the debounced call.
|
|
45
|
+
*/
|
|
46
|
+
export default {
|
|
47
|
+
component: SearchInput,
|
|
48
|
+
args: {
|
|
49
|
+
placeholder: 'Search',
|
|
50
|
+
variant: 'primary',
|
|
51
|
+
state: 'default',
|
|
52
|
+
sizing: 'default',
|
|
53
|
+
focusVariant: 'default',
|
|
54
|
+
allowClear: false,
|
|
55
|
+
disableDebounce: false,
|
|
56
|
+
debounceDelay: 300,
|
|
57
|
+
readOnly: false,
|
|
58
|
+
disabled: false,
|
|
59
|
+
onValueChange: action('onValueChange'),
|
|
60
|
+
onValueBlur: action('onValueBlur'),
|
|
61
|
+
onClear: action('onClear'),
|
|
62
|
+
},
|
|
63
|
+
argTypes: {
|
|
64
|
+
state: {
|
|
65
|
+
description: 'Validation state passed to the InputGroup wrapper.',
|
|
66
|
+
type: { name: 'enum', value: states },
|
|
67
|
+
},
|
|
68
|
+
variant: {
|
|
69
|
+
description: 'Visual variant of the InputGroup wrapper.',
|
|
70
|
+
type: { name: 'enum', value: variants },
|
|
71
|
+
},
|
|
72
|
+
sizing: {
|
|
73
|
+
description: 'Size of the InputGroup wrapper.',
|
|
74
|
+
type: { name: 'enum', value: sizings },
|
|
75
|
+
},
|
|
76
|
+
focusVariant: {
|
|
77
|
+
description: 'Focus ring style.',
|
|
78
|
+
type: { name: 'enum', value: focusVariants },
|
|
79
|
+
},
|
|
80
|
+
allowClear: {
|
|
81
|
+
description: 'Show a clear button when the field has a value.',
|
|
82
|
+
},
|
|
83
|
+
disableDebounce: {
|
|
84
|
+
description: 'Fire `onValueChange` on every keystroke instead of debouncing.',
|
|
85
|
+
},
|
|
86
|
+
debounceDelay: {
|
|
87
|
+
description: 'Debounce delay in milliseconds (ignored when `disableDebounce` is true).',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
} satisfies Meta<typeof SearchInput>;
|
|
91
|
+
|
|
92
|
+
export const Basic: Story = {};
|
|
93
|
+
|
|
94
|
+
export const WithClear: Story = {
|
|
95
|
+
args: {
|
|
96
|
+
defaultValue: 'hello world',
|
|
97
|
+
allowClear: true,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const Argo: Story = {
|
|
102
|
+
args: {
|
|
103
|
+
variant: 'argo',
|
|
104
|
+
sizing: 'medium',
|
|
105
|
+
placeholder: 'Search workspaces',
|
|
106
|
+
allowClear: true,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const ControlledDemo = (props: SearchInputProps) => {
|
|
111
|
+
const [value, setValue] = useState('');
|
|
112
|
+
return (
|
|
113
|
+
<SearchInput
|
|
114
|
+
{...props}
|
|
115
|
+
value={value}
|
|
116
|
+
onValueChange={(next) => {
|
|
117
|
+
action('onValueChange')(next);
|
|
118
|
+
setValue(next);
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const Controlled: Story = {
|
|
125
|
+
render: ControlledDemo,
|
|
126
|
+
args: { allowClear: true },
|
|
127
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
import { act, render, screen } from '@testing-library/react';
|
|
3
|
+
import { userEvent } from 'storybook/test';
|
|
4
|
+
import { vi } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { SearchInput } from './SearchInput';
|
|
7
|
+
|
|
8
|
+
type Props = ComponentProps<typeof SearchInput>;
|
|
9
|
+
|
|
10
|
+
const onValueChange = vi.fn();
|
|
11
|
+
const onBlur = vi.fn();
|
|
12
|
+
const onClear = vi.fn();
|
|
13
|
+
|
|
14
|
+
const getInput = () => screen.getByRole<HTMLInputElement>('textbox');
|
|
15
|
+
const getClearButton = () =>
|
|
16
|
+
screen.queryByRole<HTMLButtonElement>('button', { name: 'Clear search' });
|
|
17
|
+
|
|
18
|
+
const Wrapped = (props: Partial<Props>) => (
|
|
19
|
+
<SearchInput onValueChange={onValueChange} onValueBlur={onBlur} {...props} />
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
describe('<SearchInput />', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
onValueChange.mockClear();
|
|
25
|
+
onBlur.mockClear();
|
|
26
|
+
onClear.mockClear();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders with null value as empty string', () => {
|
|
30
|
+
render(<Wrapped value={null} />);
|
|
31
|
+
expect(getInput().value).toBe('');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders with null defaultValue as empty string', () => {
|
|
35
|
+
render(<Wrapped defaultValue={null} />);
|
|
36
|
+
expect(getInput().value).toBe('');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('works in uncontrolled mode', async () => {
|
|
40
|
+
render(<Wrapped disableDebounce defaultValue="hello" />);
|
|
41
|
+
const el = getInput();
|
|
42
|
+
expect(el.value).toBe('hello');
|
|
43
|
+
|
|
44
|
+
await act(async () => {
|
|
45
|
+
await userEvent.type(el, ' world');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(onValueChange).toHaveBeenCalledTimes(6);
|
|
49
|
+
expect(onValueChange).toHaveBeenLastCalledWith('hello world');
|
|
50
|
+
expect(el.value).toBe('hello world');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('works in controlled mode without debounce', async () => {
|
|
54
|
+
const { rerender } = render(<Wrapped disableDebounce value="hello" />);
|
|
55
|
+
const el = getInput();
|
|
56
|
+
expect(el.value).toBe('hello');
|
|
57
|
+
|
|
58
|
+
rerender(<Wrapped disableDebounce value="new value" />);
|
|
59
|
+
expect(el.value).toBe('new value');
|
|
60
|
+
|
|
61
|
+
await act(async () => {
|
|
62
|
+
await userEvent.type(el, ' test');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(onValueChange).toHaveBeenLastCalledWith('new value test');
|
|
66
|
+
expect(el.value).toBe('new value test');
|
|
67
|
+
|
|
68
|
+
rerender(<Wrapped disableDebounce value="reset" />);
|
|
69
|
+
expect(el.value).toBe('reset');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('allowClear', () => {
|
|
73
|
+
it('does not render clear button when allowClear is false', () => {
|
|
74
|
+
render(<Wrapped defaultValue="hello" allowClear={false} />);
|
|
75
|
+
expect(getClearButton()).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('renders clear button when allowClear is true and input has value', () => {
|
|
79
|
+
render(<Wrapped defaultValue="hello" allowClear />);
|
|
80
|
+
expect(getClearButton()).not.toBeNull();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('does not render clear button when disabled', () => {
|
|
84
|
+
render(<Wrapped defaultValue="hello" allowClear disabled />);
|
|
85
|
+
expect(getClearButton()).toBeNull();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('does not render clear button when readOnly', () => {
|
|
89
|
+
render(<Wrapped defaultValue="hello" allowClear readOnly />);
|
|
90
|
+
expect(getClearButton()).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('clears value and calls onValueChange and onClear', async () => {
|
|
94
|
+
render(<Wrapped defaultValue="hello" allowClear onClear={onClear} />);
|
|
95
|
+
const clearBtn = getClearButton()!;
|
|
96
|
+
|
|
97
|
+
await act(async () => {
|
|
98
|
+
await userEvent.click(clearBtn);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(getInput().value).toBe('');
|
|
102
|
+
expect(onValueChange).toHaveBeenCalledWith('');
|
|
103
|
+
expect(onClear).toHaveBeenCalledTimes(1);
|
|
104
|
+
expect(getClearButton()).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('debounce', () => {
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
vi.useFakeTimers();
|
|
111
|
+
onValueChange.mockClear();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
afterEach(() => {
|
|
115
|
+
vi.useRealTimers();
|
|
116
|
+
vi.clearAllTimers();
|
|
117
|
+
vi.resetAllMocks();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('debounces onValueChange by 300 ms', async () => {
|
|
121
|
+
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime, delay: 10 });
|
|
122
|
+
render(<Wrapped debounceDelay={300} />);
|
|
123
|
+
const el = getInput();
|
|
124
|
+
|
|
125
|
+
await act(async () => {
|
|
126
|
+
await user.type(el, 'test');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(onValueChange).not.toHaveBeenCalled();
|
|
130
|
+
|
|
131
|
+
vi.advanceTimersByTime(300);
|
|
132
|
+
expect(onValueChange).toHaveBeenCalledTimes(1);
|
|
133
|
+
expect(onValueChange).toHaveBeenCalledWith('test');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('cancels pending debounce when clear is clicked', async () => {
|
|
137
|
+
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime, delay: 10 });
|
|
138
|
+
render(<Wrapped debounceDelay={300} defaultValue="hello" allowClear />);
|
|
139
|
+
const el = getInput();
|
|
140
|
+
|
|
141
|
+
// type — debounce starts
|
|
142
|
+
await act(async () => {
|
|
143
|
+
await user.type(el, ' world');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(onValueChange).not.toHaveBeenCalled();
|
|
147
|
+
|
|
148
|
+
// clear before debounce fires
|
|
149
|
+
await act(async () => {
|
|
150
|
+
await user.click(getClearButton()!);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// clear fires onValueChange('') immediately
|
|
154
|
+
expect(onValueChange).toHaveBeenCalledTimes(1);
|
|
155
|
+
expect(onValueChange).toHaveBeenCalledWith('');
|
|
156
|
+
|
|
157
|
+
// advancing timer must NOT fire the stale debounced call
|
|
158
|
+
vi.advanceTimersByTime(300);
|
|
159
|
+
expect(onValueChange).toHaveBeenCalledTimes(1);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|