frappe-ui 1.0.0-beta.2 → 1.0.0-beta.21
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/experimental/FloatingWindow/FloatingWindow.cy.ts +328 -0
- package/experimental/FloatingWindow/FloatingWindow.vue +303 -0
- package/experimental/FloatingWindow/index.ts +8 -0
- package/experimental/FloatingWindow/types.ts +31 -0
- package/experimental/FloatingWindow/useFloatingWindow.ts +236 -0
- package/experimental/MultiEmailInput/MultiEmailInput.cy.ts +155 -0
- package/experimental/MultiEmailInput/MultiEmailInput.vue +472 -0
- package/experimental/MultiEmailInput/index.ts +17 -0
- package/experimental/MultiEmailInput/stories/AsyncSuggestions.vue +54 -0
- package/experimental/MultiEmailInput/stories/Basic.vue +18 -0
- package/experimental/MultiEmailInput/stories/CustomChip.vue +39 -0
- package/experimental/MultiEmailInput/stories/Labeling.vue +28 -0
- package/experimental/MultiEmailInput/types.ts +138 -0
- package/experimental/MultiEmailInput/utils.ts +60 -0
- package/experimental.ts +12 -0
- package/frappe/Billing/SignupBanner.vue +1 -1
- package/frappe/Billing/TrialBanner.vue +1 -1
- package/frappe/DataImport/DataImport.vue +1 -1
- package/frappe/DataImport/DataImportList.vue +1 -1
- package/frappe/DataImport/ImportSteps.vue +6 -6
- package/frappe/DataImport/MappingStep.vue +1 -1
- package/frappe/DataImport/PreviewStep.vue +9 -9
- package/frappe/DataImport/TemplateModal.vue +1 -1
- package/frappe/DataImport/UploadStep.vue +5 -5
- package/frappe/Filter/Filter.vue +1 -1
- package/frappe/Help/HelpModal.vue +1 -1
- package/frappe/Link/stories/Suffix.vue +2 -2
- package/frappe/Onboarding/GettingStartedBanner.vue +1 -1
- package/frappe/drive/components/AppSwitcher.vue +1 -1
- package/frappe/drive/components/InfoDialog.vue +3 -3
- package/frappe/drive/components/MoveDialog.vue +2 -2
- package/frappe/drive/components/Select/Select.vue +2 -2
- package/frappe/drive/components/ShareDialog.vue +1 -1
- package/frappe/telemetry/index.ts +88 -14
- package/frappe/telemetry/pulse.ts +78 -164
- package/frappe/telemetry/telemetry.test.ts +13 -79
- package/icons/IconPicker.vue +1 -1
- package/package.json +104 -34
- package/src/components/Alert/Alert.md +4 -0
- package/src/components/Alert/Alert.vue +4 -4
- package/src/components/Autocomplete/Autocomplete.vue +6 -6
- package/src/components/Avatar/Avatar.md +4 -0
- package/src/components/Avatar/Avatar.vue +23 -4
- package/src/components/Badge/Badge.api.md +1 -1
- package/src/components/Badge/Badge.cy.ts +34 -16
- package/src/components/Badge/Badge.md +20 -4
- package/src/components/Badge/Badge.vue +60 -45
- package/src/components/Badge/stories/CallMeta.vue +35 -0
- package/src/components/Badge/stories/EventStatus.vue +16 -0
- package/src/components/Badge/stories/ListStatus.vue +91 -0
- package/src/components/Badge/stories/Reactions.vue +33 -0
- package/src/components/Badge/stories/RowTags.vue +39 -0
- package/src/components/Badge/types.ts +1 -2
- package/src/components/BottomSheet/BottomSheet.api.md +57 -0
- package/src/components/BottomSheet/BottomSheet.vue +232 -0
- package/src/components/BottomSheet/index.ts +6 -0
- package/src/components/BottomSheet/types.ts +23 -0
- package/src/components/Breadcrumbs/Breadcrumbs.md +4 -0
- package/src/components/Breadcrumbs/Breadcrumbs.vue +3 -3
- package/src/components/Button/Button.api.md +37 -32
- package/src/components/Button/Button.cy.ts +18 -2
- package/src/components/Button/Button.md +20 -8
- package/src/components/Button/Button.vue +304 -284
- package/src/components/Button/stories/InlineActions.vue +10 -0
- package/src/components/Button/stories/LiveClassCard.vue +34 -0
- package/src/components/Button/stories/SectionAction.vue +17 -0
- package/src/components/Button/stories/SectionControls.vue +28 -0
- package/src/components/Button/stories/SelectionToolbar.vue +20 -0
- package/src/components/Button/stories/StackedActions.vue +10 -0
- package/src/components/Button/types.ts +38 -32
- package/src/components/Calendar/Calendar.api.md +16 -16
- package/src/components/Calendar/Calendar.vue +100 -73
- package/src/components/Calendar/CalendarDaily.vue +21 -20
- package/src/components/Calendar/CalendarMonthEvent.vue +30 -9
- package/src/components/Calendar/CalendarMonthly.vue +60 -39
- package/src/components/Calendar/CalendarTimeMarker.vue +9 -8
- package/src/components/Calendar/CalendarWeekDayEvent.vue +98 -42
- package/src/components/Calendar/CalendarWeekly.vue +77 -41
- package/src/components/Calendar/EventModalContent.vue +32 -16
- package/src/components/Calendar/NewEventModal.vue +19 -16
- package/src/components/Calendar/ShowMoreCalendarEvent.vue +11 -17
- package/src/components/Calendar/{calendarUtils.js → calendarUtils.ts} +76 -52
- package/src/components/Calendar/composables/{useCalendarData.js → useCalendarData.ts} +23 -11
- package/src/components/Calendar/composables/{useEventModal.js → useEventModal.ts} +16 -12
- package/src/components/Calendar/index.ts +12 -2
- package/src/components/Calendar/stories/CustomHeader.vue +1 -1
- package/src/components/Calendar/types.ts +106 -0
- package/src/components/Calendar/{useEventBase.js → useEventBase.ts} +40 -26
- package/src/components/Card.vue +2 -2
- package/src/components/Charts/ECharts.vue +1 -1
- package/src/components/Charts/NumberChart.vue +7 -7
- package/src/components/Charts/eChartOptions.ts +28 -13
- package/src/components/Checkbox/Checkbox.api.md +6 -0
- package/src/components/Checkbox/Checkbox.cy.ts +10 -2
- package/src/components/Checkbox/Checkbox.md +28 -0
- package/src/components/Checkbox/Checkbox.vue +9 -22
- package/src/components/Checkbox/stories/Group.vue +27 -0
- package/src/components/Checkbox/stories/HorizontalGroup.vue +24 -0
- package/src/components/Checkbox/stories/SettingRow.vue +23 -0
- package/src/components/Checkbox/stories/WithDescription.vue +27 -0
- package/src/components/Checkbox/types.ts +7 -0
- package/src/components/CodeEditor/CodeEditor.api.md +139 -0
- package/src/components/CodeEditor/CodeEditor.cy.ts +160 -0
- package/src/components/CodeEditor/CodeEditor.md +104 -0
- package/src/components/CodeEditor/CodeEditor.vue +556 -0
- package/src/components/CodeEditor/CodePreview.test.ts +49 -0
- package/src/components/CodeEditor/CodePreview.vue +45 -0
- package/src/components/CodeEditor/index.ts +17 -0
- package/src/components/CodeEditor/languages.ts +63 -0
- package/src/components/CodeEditor/stories/Default.vue +16 -0
- package/src/components/CodeEditor/stories/Labeling.vue +25 -0
- package/src/components/CodeEditor/stories/MaxHeight.vue +24 -0
- package/src/components/CodeEditor/stories/Preview.vue +36 -0
- package/src/components/CodeEditor/stories/Sizes.vue +15 -0
- package/src/components/CodeEditor/stories/Variants.vue +13 -0
- package/src/components/CodeEditor/theme.ts +272 -0
- package/src/components/CodeEditor/types.ts +69 -0
- package/src/components/Combobox/Combobox.api.md +19 -20
- package/src/components/Combobox/Combobox.cy.ts +54 -14
- package/src/components/Combobox/Combobox.md +4 -0
- package/src/components/Combobox/Combobox.vue +233 -247
- package/src/components/Combobox/ComboboxResults.vue +1 -1
- package/src/components/Combobox/stories/Clearable.vue +2 -2
- package/src/components/Combobox/stories/CreateNew.vue +1 -1
- package/src/components/Combobox/stories/Footer.vue +10 -2
- package/src/components/Combobox/stories/InDialog.vue +1 -1
- package/src/components/Combobox/types.ts +8 -29
- package/src/components/CommandPalette/CommandPaletteItem.vue +1 -1
- package/src/components/ContextMenu/ContextMenu.api.md +52 -0
- package/src/components/ContextMenu/ContextMenu.cy.ts +191 -0
- package/src/components/ContextMenu/ContextMenu.md +25 -0
- package/src/components/ContextMenu/ContextMenu.vue +106 -0
- package/src/components/ContextMenu/index.ts +2 -0
- package/src/components/ContextMenu/stories/FileList.vue +105 -0
- package/src/components/ContextMenu/stories/Groups.vue +95 -0
- package/src/components/ContextMenu/stories/Simple.vue +72 -0
- package/src/components/ContextMenu/types.ts +39 -0
- package/src/components/DatePicker/CalendarPanel.vue +128 -86
- package/src/components/DatePicker/DatePicker.vue +3 -9
- package/src/components/DatePicker/DateRangePicker.vue +4 -8
- package/src/components/DatePicker/DateTimePicker.cy.ts +4 -2
- package/src/components/DatePicker/DateTimePicker.vue +3 -9
- package/src/components/DatePicker/composables.ts +15 -37
- package/src/components/DatePicker/stories/Range.vue +1 -1
- package/src/components/DatePicker/types.ts +1 -1
- package/src/components/DesktopShell/DesktopShell.api.md +40 -0
- package/src/components/DesktopShell/DesktopShell.cy.ts +39 -0
- package/src/components/DesktopShell/DesktopShell.md +31 -0
- package/src/components/DesktopShell/DesktopShell.vue +81 -0
- package/src/components/DesktopShell/index.ts +3 -0
- package/src/components/DesktopShell/stories/Default.vue +97 -0
- package/src/components/DesktopShell/types.ts +2 -0
- package/src/components/Dialog/Dialog.api.md +7 -8
- package/src/components/Dialog/Dialog.md +4 -0
- package/src/components/Dialog/Dialog.vue +8 -7
- package/src/components/Dialog/stories/CommandPalette.vue +1 -1
- package/src/components/Dialog/stories/Wizard.vue +1 -1
- package/src/components/Dialogs.vue +11 -9
- package/src/components/Divider/Divider.api.md +1 -1
- package/src/components/Divider/Divider.md +4 -0
- package/src/components/Dropdown/Dropdown.api.md +10 -35
- package/src/components/Dropdown/Dropdown.md +10 -6
- package/src/components/Dropdown/Dropdown.vue +33 -47
- package/src/components/Dropdown/stories/{05_KebabMenu.vue → KebabMenu.vue} +3 -1
- package/src/components/Dropdown/stories/{06_UserMenu.vue → UserMenu.vue} +1 -1
- package/src/components/Dropdown/types.ts +25 -189
- package/src/components/Duration/Duration.api.md +92 -0
- package/src/components/Duration/Duration.cy.ts +183 -0
- package/src/components/Duration/Duration.md +98 -0
- package/src/components/Duration/Duration.vue +114 -0
- package/src/components/Duration/duration.ts +185 -0
- package/src/components/Duration/index.ts +8 -0
- package/src/components/Duration/stories/Default.vue +10 -0
- package/src/components/Duration/stories/Formats.vue +16 -0
- package/src/components/Duration/stories/RealWorld.vue +50 -0
- package/src/components/Duration/stories/Sizes.vue +15 -0
- package/src/components/Duration/stories/States.vue +21 -0
- package/src/components/Duration/types.ts +52 -0
- package/src/components/ErrorMessage/ErrorMessage.cy.ts +1 -1
- package/src/components/ErrorMessage/ErrorMessage.md +4 -0
- package/src/components/ErrorMessage/ErrorMessage.vue +1 -1
- package/src/components/FileUploader/FileUploader.api.md +36 -11
- package/src/components/FileUploader/FileUploader.cy.ts +66 -0
- package/src/components/FileUploader/FileUploader.md +4 -1
- package/src/components/FileUploader/FileUploader.vue +147 -106
- package/src/components/FileUploader/index.ts +6 -0
- package/src/components/FileUploader/types.ts +43 -0
- package/src/components/FormControl/FormControl.api.md +3 -4
- package/src/components/FormControl/FormControl.md +4 -0
- package/src/components/FormControl/stories/RealForm.vue +3 -3
- package/src/components/FormLabel.vue +5 -8
- package/src/components/HoverCard/HoverCard.api.md +100 -0
- package/src/components/HoverCard/HoverCard.cy.ts +103 -0
- package/src/components/HoverCard/HoverCard.md +52 -0
- package/src/components/HoverCard/HoverCard.vue +91 -0
- package/src/components/HoverCard/index.ts +2 -0
- package/src/components/HoverCard/stories/Arrow.vue +28 -0
- package/src/components/HoverCard/stories/Delays.vue +16 -0
- package/src/components/HoverCard/stories/Example.vue +28 -0
- package/src/components/HoverCard/types.ts +47 -0
- package/src/components/Icon/Icon.cy.ts +78 -0
- package/src/components/Icon/Icon.vue +50 -0
- package/src/components/Icon/index.ts +1 -0
- package/src/components/InputLabeling/InputDescription.vue +1 -1
- package/src/components/InputLabeling/InputError.vue +1 -1
- package/src/components/InputLabeling/InputLabel.vue +21 -7
- package/src/components/InputLabeling/RequiredIndicator.vue +1 -1
- package/src/components/ItemListRow/ItemListRow.vue +1 -1
- package/src/components/ItemListRow/stories/RowStates.vue +1 -1
- package/src/components/KeyboardShortcut.vue +4 -8
- package/src/components/KeyboardShortcutsModal/KeyboardShortcutsModal.api.md +22 -14
- package/src/components/KeyboardShortcutsModal/KeyboardShortcutsModal.vue +2 -2
- package/src/components/ListFilter/ListFilter.api.md +5 -6
- package/src/components/ListFilter/ListFilter.vue +161 -57
- package/src/components/ListFilter/NestedPopover.vue +17 -10
- package/src/components/ListFilter/SearchComplete.vue +31 -38
- package/src/components/ListItem.vue +1 -1
- package/src/components/ListView/ListEmptyState.vue +1 -1
- package/src/components/ListView/ListGroupHeader.vue +2 -2
- package/src/components/ListView/ListRow.vue +1 -1
- package/src/components/ListView/ListSelectBanner.vue +1 -1
- package/src/components/ListView/ListView.api.md +322 -6
- package/src/components/ListView/index.ts +25 -0
- package/src/components/ListView/stories/CustomList.vue +1 -1
- package/src/components/ListView/stories/GroupedRows.vue +1 -1
- package/src/components/LoadingIndicator.vue +13 -22
- package/src/components/LoadingText.vue +3 -13
- package/src/components/{Dropdown/DropdownMenuList.vue → Menu/Menu.vue} +110 -75
- package/src/components/{Dropdown/DropdownMenuItemContent.vue → Menu/MenuItemContent.vue} +23 -33
- package/src/components/{Dropdown/DropdownRenderContent.vue → Menu/MenuRenderContent.vue} +1 -1
- package/src/components/Menu/types.ts +225 -0
- package/src/components/{Dropdown → Menu}/utils.ts +36 -40
- package/src/components/MobileNav/MobileNav.api.md +70 -0
- package/src/components/MobileNav/MobileNav.cy.ts +89 -0
- package/src/components/MobileNav/MobileNav.md +34 -0
- package/src/components/MobileNav/MobileNav.vue +15 -0
- package/src/components/MobileNav/MobileNavItem.vue +125 -0
- package/src/components/MobileNav/index.ts +4 -0
- package/src/components/MobileNav/stories/Default.vue +40 -0
- package/src/components/MobileNav/types.ts +33 -0
- package/src/components/MobileShell/MobileShell.api.md +23 -0
- package/src/components/MobileShell/MobileShell.cy.ts +25 -0
- package/src/components/MobileShell/MobileShell.md +24 -0
- package/src/components/MobileShell/MobileShell.vue +41 -0
- package/src/components/MobileShell/index.ts +3 -0
- package/src/components/MobileShell/stories/Default.vue +75 -0
- package/src/components/MobileShell/types.ts +7 -0
- package/src/components/MonthPicker/MonthPicker.cy.ts +16 -0
- package/src/components/MonthPicker/MonthPicker.vue +19 -8
- package/src/components/MultiSelect/MultiSelect.api.md +3 -4
- package/src/components/MultiSelect/MultiSelect.md +42 -10
- package/src/components/MultiSelect/MultiSelect.vue +199 -227
- package/src/components/MultiSelect/MultiSelectResults.vue +1 -1
- package/src/components/MultiSelect/stories/Members.vue +1 -1
- package/src/components/MultiSelect/stories/TagsTrigger.vue +3 -3
- package/src/components/MultiSelect/types.ts +3 -3
- package/src/components/PageHeader/PageHeader.api.md +112 -0
- package/src/components/PageHeader/PageHeader.cy.ts +68 -0
- package/src/components/PageHeader/PageHeader.md +25 -0
- package/src/components/PageHeader/PageHeader.vue +13 -0
- package/src/components/PageHeader/PageHeaderBackButton.vue +29 -0
- package/src/components/PageHeader/PageHeaderBase.vue +51 -0
- package/src/components/PageHeader/PageHeaderMobile.vue +68 -0
- package/src/components/PageHeader/PageHeaderMobileTitle.vue +16 -0
- package/src/components/PageHeader/PageHeaderTarget.vue +13 -0
- package/src/components/PageHeader/PageHeaderTitle.vue +12 -0
- package/src/components/PageHeader/index.ts +7 -0
- package/src/components/PageHeader/stories/Default.vue +19 -0
- package/src/components/PageHeader/stories/Mobile.vue +24 -0
- package/src/components/PageHeader/target.ts +19 -0
- package/src/components/PageHeader/types.ts +19 -0
- package/src/components/Password/Password.md +4 -0
- package/src/components/Password/Password.vue +2 -2
- package/src/components/Popover/Popover.api.md +126 -42
- package/src/components/Popover/Popover.cy.ts +425 -41
- package/src/components/Popover/Popover.md +99 -4
- package/src/components/Popover/Popover.vue +288 -215
- package/src/components/Popover/index.ts +8 -1
- package/src/components/Popover/stories/Arrow.vue +16 -0
- package/src/components/Popover/stories/Bare.vue +20 -0
- package/src/components/Popover/stories/Click.vue +7 -4
- package/src/components/Popover/stories/Controlled.vue +22 -0
- package/src/components/Popover/stories/Dismissible.vue +19 -0
- package/src/components/Popover/stories/MatchTriggerWidth.vue +16 -0
- package/src/components/Popover/stories/SideAlign.vue +17 -0
- package/src/components/Popover/types.ts +101 -23
- package/src/components/Progress/Progress.cy.ts +2 -2
- package/src/components/Progress/Progress.md +4 -0
- package/src/components/Progress/Progress.vue +4 -4
- package/src/components/Rail/Rail.api.md +91 -0
- package/src/components/Rail/Rail.cy.ts +84 -0
- package/src/components/Rail/Rail.md +30 -0
- package/src/components/Rail/Rail.vue +16 -0
- package/src/components/Rail/RailItem.vue +134 -0
- package/src/components/Rail/RailItemBadge.vue +64 -0
- package/src/components/Rail/index.ts +2 -0
- package/src/components/Rail/stories/Default.vue +44 -0
- package/src/components/Rail/types.ts +37 -0
- package/src/components/Rating/Rating.md +4 -0
- package/src/components/Rating/Rating.vue +5 -8
- package/src/components/Rating/stories/CustomSlot.vue +2 -2
- package/src/components/ScrollArea/ScrollArea.api.md +59 -0
- package/src/components/ScrollArea/ScrollArea.vue +46 -0
- package/src/components/ScrollArea/ScrollBar.vue +101 -0
- package/src/components/ScrollArea/index.ts +2 -0
- package/src/components/Select/Select.cy.ts +16 -15
- package/src/components/Select/Select.md +4 -0
- package/src/components/Select/Select.vue +217 -203
- package/src/components/Select/stories/Example.vue +4 -3
- package/src/components/Select/stories/States.vue +1 -1
- package/src/components/Select/stories/TriggerSlots.vue +2 -2
- package/src/components/Select/utils.ts +4 -3
- package/src/components/SettingsDialog/SettingsBody.vue +23 -0
- package/src/components/SettingsDialog/SettingsContent.vue +7 -0
- package/src/components/SettingsDialog/SettingsDialog.api.md +273 -0
- package/src/components/SettingsDialog/SettingsDialog.cy.ts +126 -0
- package/src/components/SettingsDialog/SettingsDialog.md +43 -0
- package/src/components/SettingsDialog/SettingsDialog.vue +76 -0
- package/src/components/SettingsDialog/SettingsHeader.vue +36 -0
- package/src/components/SettingsDialog/SettingsNavGroup.vue +20 -0
- package/src/components/SettingsDialog/SettingsNavItem.vue +33 -0
- package/src/components/SettingsDialog/SettingsPanel.cy.ts +77 -0
- package/src/components/SettingsDialog/SettingsPanel.vue +26 -0
- package/src/components/SettingsDialog/SettingsRow.vue +52 -0
- package/src/components/SettingsDialog/SettingsSidebar.vue +16 -0
- package/src/components/SettingsDialog/index.ts +10 -0
- package/src/components/SettingsDialog/stories/Default.vue +99 -0
- package/src/components/SettingsDialog/stories/PanelBasic.vue +52 -0
- package/src/components/SettingsDialog/stories/panels/NotificationsPanel.vue +57 -0
- package/src/components/SettingsDialog/stories/panels/PreferencesPanel.vue +57 -0
- package/src/components/SettingsDialog/stories/panels/ProfilePanel.vue +38 -0
- package/src/components/SettingsDialog/stories/panels/UsersPanel.vue +83 -0
- package/src/components/SettingsDialog/types.ts +21 -0
- package/src/components/Sidebar/Sidebar.api.md +124 -65
- package/src/components/Sidebar/Sidebar.cy.ts +127 -76
- package/src/components/Sidebar/Sidebar.md +47 -3
- package/src/components/Sidebar/Sidebar.vue +64 -56
- package/src/components/Sidebar/SidebarCollapseToggle.vue +23 -0
- package/src/components/Sidebar/SidebarHeader.vue +76 -52
- package/src/components/Sidebar/SidebarItem.vue +159 -95
- package/src/components/Sidebar/SidebarItemIcon.vue +25 -0
- package/src/components/Sidebar/SidebarLabel.vue +34 -0
- package/src/components/Sidebar/SidebarSection.vue +29 -14
- package/src/components/Sidebar/index.ts +13 -2
- package/src/components/Sidebar/stories/Collapsed.vue +40 -0
- package/src/components/Sidebar/stories/Default.vue +153 -0
- package/src/components/Sidebar/stories/Legacy.vue +36 -0
- package/src/components/Sidebar/types.ts +105 -20
- package/src/components/Skeleton/Skeleton.cy.ts +21 -0
- package/src/components/Skeleton/Skeleton.md +21 -0
- package/src/components/Skeleton/Skeleton.vue +6 -0
- package/src/components/Skeleton/index.ts +1 -0
- package/src/components/Skeleton/stories/Card.vue +12 -0
- package/src/components/Skeleton/stories/List.vue +15 -0
- package/src/components/Skeleton/stories/Text.vue +11 -0
- package/src/components/Slider/Slider.api.md +1 -1
- package/src/components/Slider/Slider.cy.ts +29 -0
- package/src/components/Slider/Slider.md +10 -0
- package/src/components/Slider/Slider.vue +37 -9
- package/src/components/Slider/stories/NegativeValues.vue +12 -0
- package/src/components/Slider/types.ts +1 -1
- package/src/components/Spinner/Spinner.api.md +32 -0
- package/src/components/Spinner/Spinner.cy.ts +87 -0
- package/src/components/Spinner/Spinner.md +21 -0
- package/src/components/Spinner/Spinner.vue +168 -0
- package/src/components/Spinner/index.ts +2 -0
- package/src/components/Spinner/stories/InContext.vue +49 -0
- package/src/components/Spinner/stories/Sizes.vue +12 -0
- package/src/components/Spinner/stories/Themes.vue +10 -0
- package/src/components/Spinner/stories/Track.vue +12 -0
- package/src/components/Spinner/types.ts +14 -0
- package/src/components/Switch/Switch.md +4 -0
- package/src/components/Switch/Switch.vue +6 -23
- package/src/components/TabButtons/Pill.vue +161 -0
- package/src/components/TabButtons/TabButtons.api.md +52 -2
- package/src/components/TabButtons/TabButtons.cy.ts +18 -5
- package/src/components/TabButtons/TabButtons.md +49 -0
- package/src/components/TabButtons/TabButtons.vue +207 -109
- package/src/components/TabButtons/index.ts +11 -1
- package/src/components/TabButtons/pillTypes.ts +43 -0
- package/src/components/TabButtons/stories/PrefixSuffix.vue +65 -0
- package/src/components/TabButtons/stories/Sizes.vue +26 -0
- package/src/components/TabButtons/stories/Variants.vue +40 -0
- package/src/components/TabButtons/stories/Vertical.vue +64 -0
- package/src/components/TabButtons/types.ts +49 -0
- package/src/components/Tabs/Tabs.md +4 -0
- package/src/components/Tabs/Tabs.vue +1 -1
- package/src/components/TextEditor/TextEditor.cy.ts +1 -1
- package/src/components/TextEditor/TextEditor.md +3 -0
- package/src/components/TextEditor/TextEditor.vue +7 -4
- package/src/components/TextEditor/components/CodeBlockComponent.vue +4 -194
- package/src/components/TextEditor/components/FontColor.vue +1 -1
- package/src/components/TextEditor/components/LinkPopup.vue +1 -1
- package/src/components/TextEditor/components/MediaNodeView.vue +17 -17
- package/src/components/TextEditor/components/MentionList.vue +1 -1
- package/src/components/TextEditor/components/TextEditorBubbleMenu.vue +14 -5
- package/src/components/TextEditor/components/TextEditorFixedMenu.vue +3 -3
- package/src/components/TextEditor/components/{Menu.vue → TextEditorMenu.vue} +3 -3
- package/src/components/TextEditor/extensions/iframe/InsertIframe.vue +2 -2
- package/src/components/TextEditor/extensions/iframe/iframe-extension.ts +62 -19
- package/src/components/TextEditor/extensions/image-group/ImageGroupNodeView.vue +1 -1
- package/src/components/TextEditor/extensions/image-group/ImageGroupUploadDialog.vue +4 -4
- package/src/components/TextEditor/extensions/suggestion/SuggestionList.vue +1 -1
- package/src/components/TextEditor/extensions/toc-node/TocNodeView.vue +1 -1
- package/src/components/TextEditor/hljs-github.css +197 -0
- package/src/components/TextEditor/index.ts +13 -0
- package/src/components/TextInput/TextInput.cy.ts +8 -3
- package/src/components/TextInput/TextInput.md +4 -0
- package/src/components/TextInput/TextInput.vue +11 -15
- package/src/components/TextInput/stories/Slots.vue +1 -1
- package/src/components/Textarea/Textarea.cy.ts +1 -1
- package/src/components/Textarea/Textarea.md +4 -0
- package/src/components/Textarea/Textarea.vue +11 -15
- package/src/components/ThemeSwitcher/ThemePreview.vue +74 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.api.md +82 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.cy.ts +101 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.md +70 -0
- package/src/components/ThemeSwitcher/ThemeSwitcher.vue +145 -0
- package/src/components/ThemeSwitcher/index.ts +2 -0
- package/src/components/ThemeSwitcher/previews/PreviewWindow.vue +74 -0
- package/src/components/ThemeSwitcher/stories/Branded.vue +12 -0
- package/src/components/ThemeSwitcher/stories/Default.vue +7 -0
- package/src/components/ThemeSwitcher/stories/Menu.vue +53 -0
- package/src/components/ThemeSwitcher/stories/Toggle.vue +25 -0
- package/src/components/ThemeSwitcher/types.ts +35 -0
- package/src/components/TimePicker/TimePicker.api.md +67 -15
- package/src/components/TimePicker/TimePicker.cy.ts +130 -14
- package/src/components/TimePicker/TimePicker.md +6 -0
- package/src/components/TimePicker/TimePicker.vue +74 -19
- package/src/components/TimePicker/stories/Labeling.vue +21 -0
- package/src/components/TimePicker/stories/Range.vue +1 -1
- package/src/components/TimePicker/stories/SizesAndVariants.vue +23 -0
- package/src/components/TimePicker/stories/TwentyFour.vue +1 -1
- package/src/components/TimePicker/types.ts +15 -3
- package/src/components/TimePicker/utils.ts +82 -21
- package/src/components/Toast/Toast.md +1 -1
- package/src/components/Toast/Toast.sanitize.test.ts +50 -0
- package/src/components/Toast/Toast.test.ts +5 -0
- package/src/components/Toast/Toast.vue +7 -7
- package/src/components/Toast/ToastProvider.vue +18 -9
- package/src/components/Toast/stories/CustomIcon.vue +1 -1
- package/src/components/Toast/toast.ts +32 -8
- package/src/components/Tooltip/Tooltip.api.md +43 -5
- package/src/components/Tooltip/Tooltip.cy.ts +48 -0
- package/src/components/Tooltip/Tooltip.md +10 -0
- package/src/components/Tooltip/Tooltip.vue +20 -5
- package/src/components/Tooltip/TooltipBubble.vue +2 -2
- package/src/components/Tooltip/TooltipProvider.vue +53 -0
- package/src/components/Tooltip/index.ts +1 -0
- package/src/components/Tooltip/stories/Group.vue +22 -0
- package/src/components/Tooltip/stories/Slots.vue +2 -2
- package/src/components/Tooltip/types.ts +1 -1
- package/src/components/Tree/Tree.api.md +76 -18
- package/src/components/Tree/Tree.md +144 -2
- package/src/components/Tree/Tree.vue +276 -97
- package/src/components/Tree/TreeItem.vue +263 -0
- package/src/components/Tree/index.ts +9 -1
- package/src/components/Tree/stories/DragDrop.vue +94 -0
- package/src/components/Tree/stories/Example.vue +11 -34
- package/src/components/Tree/stories/ExpandAll.vue +48 -0
- package/src/components/Tree/stories/Guides.vue +49 -0
- package/src/components/Tree/stories/ListView.vue +74 -0
- package/src/components/Tree/tree.cy.ts +218 -90
- package/src/components/Tree/types.ts +114 -24
- package/src/components/Tree/useTreeDragDrop.ts +203 -0
- package/src/components/Tree/useTreeKeyboard.ts +109 -0
- package/src/components/VueGridLayout/Layout.vue +1 -1
- package/src/components/shared/picker/PickerShell.vue +20 -15
- package/src/components/shared/popover/PopoverPanel.vue +67 -0
- package/src/components/shared/popover/popoverPanel.css +102 -0
- package/src/components/shared/selection/utils.ts +6 -6
- package/src/composables/useAutofocusOnOpen.ts +2 -0
- package/src/composables/useScreenSize.ts +46 -0
- package/src/composables/useScrollContainer.ts +108 -0
- package/src/composables/useShortcut.ts +3 -0
- package/src/data-fetching/docStore.test.ts +74 -0
- package/src/data-fetching/docStore.ts +18 -7
- package/src/data-fetching/useCall/types.ts +1 -0
- package/src/data-fetching/useCall/useCall.test.ts +43 -0
- package/src/data-fetching/useCall/useCall.ts +17 -4
- package/src/data-fetching/useDoc/useDoc.test.ts +61 -2
- package/src/data-fetching/useDoc/useDoc.ts +35 -15
- package/src/data-fetching/useDoctype/useDoctype.ts +6 -5
- package/src/data-fetching/useFrappeFetch.ts +17 -4
- package/src/data-fetching/useList/types.ts +1 -0
- package/src/data-fetching/useList/useList.test.ts +40 -0
- package/src/data-fetching/useList/useList.ts +20 -13
- package/src/data-fetching/useNewDoc/useNewDoc.ts +9 -5
- package/src/directives/focus.ts +3 -3
- package/src/index.ts +105 -74
- package/src/mocks/handlers.ts +8 -0
- package/src/molecules/editor/Editor.cy.ts +6 -1
- package/src/molecules/editor/EditorBubbleMenu.vue +1 -1
- package/src/molecules/editor/EditorContent.vue +50 -9
- package/src/molecules/editor/EditorFixedMenu.vue +2 -1
- package/src/molecules/editor/EditorFloatingMenu.vue +1 -1
- package/src/molecules/editor/EditorTableMenu.vue +303 -0
- package/src/molecules/editor/MenuItems.vue +81 -69
- package/src/molecules/editor/commands.ts +10 -2
- package/src/molecules/editor/components/AttachmentNodeView.vue +115 -0
- package/src/molecules/editor/components/EditorDropZone.vue +105 -0
- package/src/molecules/editor/components/EditorPopover.vue +89 -0
- package/src/molecules/editor/components/MediaNodeView.vue +156 -49
- package/src/molecules/editor/components/MediaResizeHandles.vue +38 -0
- package/src/molecules/editor/components/MediaToolbar.vue +108 -54
- package/src/molecules/editor/components/TableContextMenu.vue +144 -0
- package/src/molecules/editor/components/UploadProgressIndicator.vue +33 -0
- package/src/molecules/editor/components/VideoControls.vue +208 -0
- package/src/molecules/editor/components/font-color/ColorSwatchGrid.vue +3 -5
- package/src/molecules/editor/components/font-color/fontColorController.ts +28 -23
- package/src/molecules/editor/components/font-color/swatches.ts +1 -1
- package/src/molecules/editor/components/media-node-view-controller.ts +4 -4
- package/src/molecules/editor/components/media-node-view-utils.test.ts +0 -13
- package/src/molecules/editor/components/media-node-view-utils.ts +0 -10
- package/src/molecules/editor/components/table-color/tableCellColorController.ts +80 -0
- package/src/molecules/editor/components/table-size-picker/TableSizePicker.vue +92 -0
- package/src/molecules/editor/components/table-size-picker/tableSizePickerController.ts +40 -0
- package/src/molecules/editor/composables/useEditorFileDrop.ts +117 -0
- package/src/molecules/editor/composables/useFloatingPopup.ts +46 -2
- package/src/molecules/editor/composables/useNodeViewResize.test.ts +60 -11
- package/src/molecules/editor/composables/useNodeViewResize.ts +80 -21
- package/src/molecules/editor/composables/useTableCellColorState.ts +78 -0
- package/src/molecules/editor/composables/useWindowFileDragging.ts +68 -0
- package/src/molecules/editor/extensions/attachment/attachment-engine.ts +250 -0
- package/src/molecules/editor/extensions/attachment/attachment-extension.ts +248 -0
- package/src/molecules/editor/extensions/attachment/attachment.flow.test.ts +80 -0
- package/src/molecules/editor/extensions/attachment/attachment.roundtrip.test.ts +50 -0
- package/src/molecules/editor/extensions/attachment/index.ts +5 -0
- package/src/molecules/editor/extensions/code-block/CodeBlockComponent.css +16 -26
- package/src/molecules/editor/extensions/code-block/CodeBlockComponent.vue +107 -22
- package/src/molecules/editor/extensions/content-paste/content-paste-extension.ts +5 -3
- package/src/molecules/editor/extensions/iframe/IframeInsertDialog.vue +34 -7
- package/src/molecules/editor/extensions/iframe/IframeNodeView.vue +54 -48
- package/src/molecules/editor/extensions/iframe/iframe-allowlist.ts +33 -4
- package/src/molecules/editor/extensions/iframe/iframe-commands.ts +49 -5
- package/src/molecules/editor/extensions/iframe/iframe-embed-utils.ts +30 -0
- package/src/molecules/editor/extensions/iframe/iframe-extension.ts +4 -4
- package/src/molecules/editor/extensions/iframe/iframeInsertDialogController.ts +12 -1
- package/src/molecules/editor/extensions/iframe/index.ts +4 -4
- package/src/molecules/editor/extensions/iframe/useIframeDialog.ts +25 -13
- package/src/molecules/editor/extensions/image/image-extension.ts +29 -11
- package/src/molecules/editor/extensions/image-group/ImageGroupGrid.vue +27 -11
- package/src/molecules/editor/extensions/image-group/ImageGroupGridCell.vue +55 -11
- package/src/molecules/editor/extensions/image-group/ImageGroupNodeView.vue +64 -31
- package/src/molecules/editor/extensions/image-group/ImageGroupUploadDialog.vue +84 -40
- package/src/molecules/editor/extensions/image-group/image-group-extension.ts +4 -3
- package/src/molecules/editor/extensions/image-group/useImageGroupDialog.ts +116 -17
- package/src/molecules/editor/extensions/link/LinkEditorPopup.vue +214 -71
- package/src/molecules/editor/extensions/link/link-click-plugin.ts +10 -15
- package/src/molecules/editor/extensions/link/link-commands.ts +24 -3
- package/src/molecules/editor/extensions/link/link-extension.ts +11 -2
- package/src/molecules/editor/extensions/link/link-popup-controller.test.ts +49 -0
- package/src/molecules/editor/extensions/link/link-popup-controller.ts +67 -2
- package/src/molecules/editor/extensions/link/link-shortcut-plugin.ts +7 -2
- package/src/molecules/editor/extensions/media-drop/media-drop-extension.ts +177 -0
- package/src/molecules/editor/extensions/shared/media-dimensions.ts +36 -3
- package/src/molecules/editor/extensions/shared/media-node-ops.ts +17 -0
- package/src/molecules/editor/extensions/shared/media-plugin.test.ts +60 -0
- package/src/molecules/editor/extensions/shared/media-plugin.ts +7 -37
- package/src/molecules/editor/extensions/shared/media-upload-engine.test.ts +39 -2
- package/src/molecules/editor/extensions/shared/media-upload-engine.ts +104 -10
- package/src/molecules/editor/extensions/shared/media-upload-state.ts +55 -0
- package/src/molecules/editor/extensions/shared/media-upload-types.ts +52 -1
- package/src/molecules/editor/extensions/shared/suggestion-helpers.ts +20 -0
- package/src/molecules/editor/extensions/shared/suggestion-types.ts +7 -0
- package/src/molecules/editor/extensions/shared/upload-types.ts +1 -0
- package/src/molecules/editor/extensions/slash-commands/slash-commands-extension.test.ts +38 -0
- package/src/molecules/editor/extensions/slash-commands/slash-commands-extension.ts +119 -55
- package/src/molecules/editor/extensions/suggestion/SuggestionList.vue +62 -22
- package/src/molecules/editor/extensions/suggestion/SuggestionListItem.vue +1 -1
- package/src/molecules/editor/extensions/suggestion/createSuggestionExtension.ts +4 -0
- package/src/molecules/editor/extensions/table/drag-scroll.ts +154 -0
- package/src/molecules/editor/extensions/table/table-cell-color.ts +188 -0
- package/src/molecules/editor/extensions/table/table-navigation.ts +560 -0
- package/src/molecules/editor/extensions/table/table-selection-overlay.ts +137 -0
- package/src/molecules/editor/extensions/toc-node/TocNodeView.vue +1 -1
- package/src/molecules/editor/extensions/toc-node/toc-render.ts +7 -3
- package/src/molecules/editor/extensions/video/video-extension.ts +72 -5
- package/src/molecules/editor/extensions.ts +176 -8
- package/src/molecules/editor/index.ts +9 -4
- package/src/molecules/editor/kits.test.ts +21 -1
- package/src/molecules/editor/kits.ts +56 -33
- package/src/molecules/editor/menu.test.ts +3 -1
- package/src/molecules/editor/menu.ts +180 -18
- package/src/molecules/editor/stories/Comment.vue +4 -4
- package/src/molecules/editor/stories/Inline.vue +1 -1
- package/src/molecules/editor/stories/Primitives.vue +1 -1
- package/src/molecules/editor/stories/RichText.vue +1 -1
- package/src/molecules/editor/style.css +80 -3
- package/src/molecules/editor/useEditor.test.ts +4 -0
- package/src/molecules/editor/useEditor.ts +7 -3
- package/src/molecules/list/List.cy.ts +626 -0
- package/src/molecules/list/List.vue +93 -0
- package/src/molecules/list/ListCell.vue +15 -0
- package/src/molecules/list/ListGroup.vue +40 -0
- package/src/molecules/list/ListHeader.vue +62 -0
- package/src/molecules/list/ListHeaderCell.vue +26 -0
- package/src/molecules/list/ListHeaderCellSort.vue +106 -0
- package/src/molecules/list/ListRow.vue +55 -0
- package/src/molecules/list/ListRowBase.vue +103 -0
- package/src/molecules/list/ListRows.vue +120 -0
- package/src/molecules/list/index.ts +24 -0
- package/src/molecules/list/list-context.ts +44 -0
- package/src/molecules/list/list.api.md +276 -0
- package/src/molecules/list/list.md +119 -0
- package/src/molecules/list/stories/Columns.vue +103 -0
- package/src/molecules/list/stories/Feed.vue +105 -0
- package/src/molecules/list/stories/Virtual.vue +31 -0
- package/src/molecules/list/style.css +173 -0
- package/src/molecules/list/types.ts +82 -0
- package/src/molecules/list/useVirtualRows.ts +80 -0
- package/src/resources/documentResource.d.ts +28 -0
- package/src/resources/documentResource.js +32 -4
- package/src/resources/documentResource.test.ts +111 -0
- package/src/resources/listResource.d.ts +33 -0
- package/src/resources/plugin.d.ts +7 -0
- package/src/resources/resources.d.ts +35 -0
- package/src/utils/{call.js → call.ts} +50 -9
- package/src/utils/config.ts +16 -1
- package/src/utils/confirmDialog.d.ts +17 -0
- package/src/utils/debounce.ts +6 -7
- package/src/utils/dialog.cy.ts +9 -9
- package/src/utils/fileSize.ts +36 -0
- package/src/utils/fileUploadHandler.ts +46 -6
- package/src/utils/frappeRequest.test.ts +79 -0
- package/src/utils/{frappeRequest.js → frappeRequest.ts} +76 -13
- package/src/utils/pageMeta.ts +1 -1
- package/src/utils/plugin.ts +58 -0
- package/src/utils/{request.js → request.ts} +28 -3
- package/src/utils/{socketio.js → socketio.ts} +11 -1
- package/src/utils/theme.ts +48 -57
- package/src/utils/useFileUpload.ts +80 -23
- package/tailwind/audit-token-drift.cjs +176 -0
- package/tailwind/colorPalette.js +69 -8
- package/tailwind/colors.json +1115 -397
- package/tailwind/figma-tokens-to-theme.js +364 -0
- package/tailwind/generated/colors.json +1217 -0
- package/tailwind/generated/effects.json +41 -0
- package/tailwind/generated/radius.json +20 -0
- package/tailwind/generated/typography.json +479 -0
- package/tailwind/migrate-tokens-v2.js +453 -0
- package/tailwind/migrate-tokens-v2.test.js +55 -0
- package/tailwind/plugin.js +220 -15
- package/tailwind/preset.js +15 -0
- package/tailwind/tokens.js +35 -37
- package/tsconfig.base.json +2 -2
- package/vitepress/components/Docs/CommandPalette.vue +214 -0
- package/vitepress/components/Docs/Demo.vue +60 -0
- package/vitepress/components/Docs/EmitsTable.vue +99 -0
- package/vitepress/components/Docs/MobileNavSheet.vue +148 -0
- package/vitepress/components/Docs/OnThisPage.vue +74 -0
- package/vitepress/components/Docs/PrevNextBtns.vue +58 -0
- package/vitepress/components/Docs/PropsTable.vue +185 -0
- package/vitepress/components/Docs/Search.vue +34 -0
- package/vitepress/components/Docs/Sidebar.vue +91 -0
- package/vitepress/components/Docs/SlotsTable.vue +99 -0
- package/vitepress/components/Docs/index.ts +13 -0
- package/vitepress/components/Docs/sidebarList.ts +33 -0
- package/vitepress/components/Layout.vue +58 -0
- package/vitepress/components/Navbar.vue +126 -0
- package/vitepress/components/index.ts +3 -0
- package/vitepress/composables/index.ts +3 -0
- package/vitepress/composables/useTheme.ts +39 -0
- package/vitepress/css/fonts/Inter-Italic.var.woff2 +0 -0
- package/vitepress/css/fonts/Inter.var.woff2 +0 -0
- package/vitepress/css/style.css +198 -0
- package/vitepress/index.d.ts +23 -0
- package/vitepress/index.node.ts +175 -0
- package/vitepress/index.ts +17 -0
- package/vitepress/plugins/colocatedComponentDocs.ts +164 -0
- package/vitepress/plugins/componentTransformer.ts +286 -0
- package/vitepress/plugins/index.ts +12 -0
- package/vitepress/state.ts +10 -0
- package/vitepress/theme/index.ts +21 -0
- package/frappe/telemetry/utils.ts +0 -15
- package/src/components/Badge/stories/Themes.vue +0 -10
- package/src/components/Badge/stories/Variants.vue +0 -10
- package/src/components/Button/stories/Icons.vue +0 -27
- package/src/components/Button/stories/Sizes.vue +0 -11
- package/src/components/Button/stories/Themes.vue +0 -10
- package/src/components/Button/stories/Variants.vue +0 -10
- package/src/components/Popover/stories/Hover.vue +0 -14
- package/src/components/Resource.vue +0 -21
- package/src/components/Sidebar/stories/Example.vue +0 -76
- package/src/components/Spinner.vue +0 -3
- package/src/components/TabButtons/TabButtons.story.vue +0 -22
- package/src/utils/plugin.js +0 -24
- /package/src/components/Dropdown/stories/{02_Shortcuts.vue → Shortcuts.vue} +0 -0
- /package/src/components/Dropdown/stories/{01_Simple.vue → Simple.vue} +0 -0
- /package/src/components/Dropdown/stories/{03_Submenus.vue → Submenus.vue} +0 -0
- /package/src/components/Dropdown/stories/{04_Switches.vue → Switches.vue} +0 -0
- /package/src/components/{Dropdown/DropdownRenderContentAsChild.vue → Menu/MenuRenderContentAsChild.vue} +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Duration
|
|
2
|
+
|
|
3
|
+
A text input for entering a length of time. The caller works in **seconds** (via
|
|
4
|
+
`v-model`); the user types a human-readable duration in any common notation and
|
|
5
|
+
the component parses it on commit.
|
|
6
|
+
|
|
7
|
+
## Playground
|
|
8
|
+
|
|
9
|
+
<ClientOnly><DurationBuilder /></ClientOnly>
|
|
10
|
+
|
|
11
|
+
<ComponentPreview name="Duration-Default" layout="stacked" />
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
Logging effort against a task: the user enters a duration in any notation and
|
|
16
|
+
the saved seconds roll up into a total.
|
|
17
|
+
|
|
18
|
+
<ComponentPreview name="Duration-RealWorld" />
|
|
19
|
+
|
|
20
|
+
## Input notation
|
|
21
|
+
|
|
22
|
+
The field accepts several notations, case-insensitive, with units in any order.
|
|
23
|
+
Parsing happens when the field is committed (blur or `Enter`):
|
|
24
|
+
|
|
25
|
+
| Notation | Example | Seconds |
|
|
26
|
+
| ------------ | -------------------------------------- | ------------------ |
|
|
27
|
+
| Short units | `1h 30m 45s`, `1h30m45s`, `45s 1h 30m` | `5445` |
|
|
28
|
+
| Long units | `1 hour 30 minutes`, `2hrs 15min` | `5400`, `8100` |
|
|
29
|
+
| Colon | `1:30:45`, `1:30`, `:45` | `5445`, `90`, `45` |
|
|
30
|
+
| Bare integer | `90` | `90` |
|
|
31
|
+
|
|
32
|
+
Invalid input keeps the field open with the typed text and an inline error so it
|
|
33
|
+
can be corrected; the saved value is left untouched until a valid commit.
|
|
34
|
+
`Escape` abandons the edit, and clearing the field commits `null`.
|
|
35
|
+
|
|
36
|
+
## Display format
|
|
37
|
+
|
|
38
|
+
When the field is not focused, the saved value is rendered using `format`. This
|
|
39
|
+
is either a **named preset** or a **token template string**. The default is the
|
|
40
|
+
`short` preset, which renders `2h 2m 3s` and omits zero parts.
|
|
41
|
+
|
|
42
|
+
### Presets
|
|
43
|
+
|
|
44
|
+
Presets are smart — they omit zero components (and `long` pluralizes):
|
|
45
|
+
|
|
46
|
+
| `format` | `5445` | `90` |
|
|
47
|
+
| ------------------- | ------------------------------ | --------------------- |
|
|
48
|
+
| `short` _(default)_ | `1h 30m 45s` | `1m 30s` |
|
|
49
|
+
| `long` | `1 hour 30 minutes 45 seconds` | `1 minute 30 seconds` |
|
|
50
|
+
| `colon` | `1:30:45` | `1:30` |
|
|
51
|
+
|
|
52
|
+
### Token templates
|
|
53
|
+
|
|
54
|
+
Any other `format` value is a template, rendered literally (no zero-omission).
|
|
55
|
+
`h`/`m`/`s` are unit tokens; double them (`hh`/`mm`/`ss`) to zero-pad to two
|
|
56
|
+
digits. Wrap label text in single quotes so the unit letters render as text.
|
|
57
|
+
|
|
58
|
+
| `format` | `7323` |
|
|
59
|
+
| ---------------- | ---------- |
|
|
60
|
+
| `h'h' m'm' s's'` | `2h 2m 3s` |
|
|
61
|
+
| `hh:mm:ss` | `02:02:03` |
|
|
62
|
+
| `h':'mm` | `2:02` |
|
|
63
|
+
|
|
64
|
+
Each token renders only its own component — `m` is the minutes-within-the-hour
|
|
65
|
+
(`2`), not the total minutes (`122`). Include every unit you want to keep, or a
|
|
66
|
+
higher unit's value is dropped from the output.
|
|
67
|
+
|
|
68
|
+
> Editing always switches to the canonical `2h 2m 3s` notation so the typed
|
|
69
|
+
> value round-trips reliably through the parser, whatever the display format.
|
|
70
|
+
|
|
71
|
+
<ComponentPreview name="Duration-Formats" />
|
|
72
|
+
|
|
73
|
+
## Sizes
|
|
74
|
+
|
|
75
|
+
<ComponentPreview name="Duration-Sizes" />
|
|
76
|
+
|
|
77
|
+
## Labeling
|
|
78
|
+
|
|
79
|
+
Duration implements the shared input labeling contract (`label`, `description`,
|
|
80
|
+
`error`, `required`), forwarded to the underlying `TextInput`.
|
|
81
|
+
|
|
82
|
+
<ComponentPreview name="Duration-States" />
|
|
83
|
+
|
|
84
|
+
## Keyboard
|
|
85
|
+
|
|
86
|
+
| Keys | Action |
|
|
87
|
+
| ------------ | --------------------------------------------- |
|
|
88
|
+
| `Enter` | Commit the typed value |
|
|
89
|
+
| `Escape` | Cancel the edit and revert to the saved value |
|
|
90
|
+
| `Tab` / blur | Commit the typed value |
|
|
91
|
+
|
|
92
|
+
## Customization
|
|
93
|
+
|
|
94
|
+
Duration renders a `TextInput`, so the same data-attribute styling hooks apply —
|
|
95
|
+
`data-slot="control"`, `data-size`, `data-disabled`, and `data-state="invalid"`
|
|
96
|
+
on error. See TextInput for the full taxonomy.
|
|
97
|
+
|
|
98
|
+
<!-- @include: ./Duration.api.md -->
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<TextInput
|
|
3
|
+
ref="inputRef"
|
|
4
|
+
:id="id"
|
|
5
|
+
:model-value="displayValue"
|
|
6
|
+
:label="label"
|
|
7
|
+
:description="description"
|
|
8
|
+
:error="error || internalError"
|
|
9
|
+
:required="required"
|
|
10
|
+
:disabled="disabled"
|
|
11
|
+
:size="size"
|
|
12
|
+
:variant="variant"
|
|
13
|
+
:placeholder="placeholder"
|
|
14
|
+
@focus="handleFocus"
|
|
15
|
+
@blur="handleBlur"
|
|
16
|
+
@input="handleInput"
|
|
17
|
+
@keydown="handleKeydown"
|
|
18
|
+
/>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { computed, nextTick, ref } from 'vue'
|
|
23
|
+
import { TextInput } from '../TextInput'
|
|
24
|
+
import { formatDuration, parseDuration } from './duration'
|
|
25
|
+
import type { DurationProps, DurationExposed } from './types'
|
|
26
|
+
|
|
27
|
+
const props = withDefaults(defineProps<DurationProps>(), {
|
|
28
|
+
placeholder: '1h 30m 45s',
|
|
29
|
+
format: 'short',
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const model = defineModel<number | null>({ default: null })
|
|
33
|
+
|
|
34
|
+
const inputRef = ref<{ el: HTMLInputElement | null } | null>(null)
|
|
35
|
+
const isFocused = ref(false)
|
|
36
|
+
const editValue = ref('')
|
|
37
|
+
const internalError = ref('')
|
|
38
|
+
const cancelled = ref(false)
|
|
39
|
+
|
|
40
|
+
const displayValue = computed(() =>
|
|
41
|
+
isFocused.value || internalError.value
|
|
42
|
+
? editValue.value
|
|
43
|
+
: formatDuration(model.value, props.format),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
function handleFocus() {
|
|
47
|
+
// Guard against redundant focus events while already editing so we don't
|
|
48
|
+
// clobber the in-progress text or re-run the select(). After an invalid
|
|
49
|
+
// commit isFocused is false, so re-focusing starts a fresh canonical edit.
|
|
50
|
+
if (isFocused.value) return
|
|
51
|
+
isFocused.value = true
|
|
52
|
+
// Always edit in the canonical "1h 30m 45s" notation so the typed value
|
|
53
|
+
// round-trips through parseDuration regardless of the display `format`.
|
|
54
|
+
editValue.value = formatDuration(model.value, 'short')
|
|
55
|
+
internalError.value = ''
|
|
56
|
+
nextTick(() => inputRef.value?.el?.select())
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function handleInput(event: Event) {
|
|
60
|
+
editValue.value = (event.target as HTMLInputElement).value
|
|
61
|
+
internalError.value = ''
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleBlur() {
|
|
65
|
+
// Escape cancels the edit: skip the commit the blur would otherwise trigger
|
|
66
|
+
// and leave the saved value untouched.
|
|
67
|
+
if (cancelled.value) {
|
|
68
|
+
cancelled.value = false
|
|
69
|
+
isFocused.value = false
|
|
70
|
+
internalError.value = ''
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
commit()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
77
|
+
if (event.key === 'Enter') {
|
|
78
|
+
event.preventDefault()
|
|
79
|
+
inputRef.value?.el?.blur()
|
|
80
|
+
} else if (event.key === 'Escape') {
|
|
81
|
+
cancelled.value = true
|
|
82
|
+
inputRef.value?.el?.blur()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function commit() {
|
|
87
|
+
const raw = editValue.value.trim()
|
|
88
|
+
|
|
89
|
+
if (raw === '') {
|
|
90
|
+
internalError.value = ''
|
|
91
|
+
isFocused.value = false
|
|
92
|
+
model.value = null
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const seconds = parseDuration(raw)
|
|
97
|
+
if (seconds === null) {
|
|
98
|
+
internalError.value =
|
|
99
|
+
'Invalid format. Try: 1h 30m 45s, 1 hour 30 minutes, 1:30:45, 90s'
|
|
100
|
+
// Report the error and let focus leave. The rejected text stays visible
|
|
101
|
+
// (displayValue tracks the error state) as a reminder, but they aren't
|
|
102
|
+
// trapped in the field. Re-focusing restarts from the canonical saved
|
|
103
|
+
// value, so the rejected text is discarded rather than edited.
|
|
104
|
+
isFocused.value = false
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
internalError.value = ''
|
|
109
|
+
isFocused.value = false
|
|
110
|
+
model.value = seconds
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
defineExpose<DurationExposed>({ focus: () => inputRef.value?.el?.focus() })
|
|
114
|
+
</script>
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Duration helpers: convert between a seconds value and human-readable text.
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import type { DurationFormat } from './types'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Format a duration in seconds as text.
|
|
9
|
+
*
|
|
10
|
+
* `format` is either a named preset — which applies smart zero-omission and
|
|
11
|
+
* (for `long`) pluralization — or a token template rendered literally:
|
|
12
|
+
*
|
|
13
|
+
* formatDuration(5445) // "1h 30m 45s" (short preset, default)
|
|
14
|
+
* formatDuration(90) // "1m 30s" (preset omits zero parts)
|
|
15
|
+
* formatDuration(5445, 'long') // "1 hour 30 minutes 45 seconds"
|
|
16
|
+
* formatDuration(5445, 'colon') // "1:30:45"
|
|
17
|
+
* formatDuration(5445, "h'h' m'm' s's'") // "1h 30m 45s" (template, all parts)
|
|
18
|
+
* formatDuration(90, "h'h' m'm' s's'") // "0h 1m 30s"
|
|
19
|
+
* formatDuration(7323, 'hh:mm:ss') // "02:02:03"
|
|
20
|
+
*/
|
|
21
|
+
export function formatDuration(
|
|
22
|
+
totalSeconds: number | null | undefined,
|
|
23
|
+
format: DurationFormat = 'short',
|
|
24
|
+
): string {
|
|
25
|
+
if (totalSeconds === null || totalSeconds === undefined) return ''
|
|
26
|
+
|
|
27
|
+
const seconds = Number(totalSeconds)
|
|
28
|
+
if (Number.isNaN(seconds)) return ''
|
|
29
|
+
|
|
30
|
+
const hours = Math.floor(seconds / 3600)
|
|
31
|
+
const minutes = Math.floor((seconds % 3600) / 60)
|
|
32
|
+
const secs = seconds % 60
|
|
33
|
+
|
|
34
|
+
if (format === 'short') return formatShort(hours, minutes, secs)
|
|
35
|
+
if (format === 'long') return formatLong(hours, minutes, secs)
|
|
36
|
+
if (format === 'colon') return formatColon(hours, minutes, secs)
|
|
37
|
+
return renderTemplate(format, hours, minutes, secs)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Token template engine.
|
|
42
|
+
//
|
|
43
|
+
// Outside single quotes, runs of h / m / s are unit tokens; a run of length
|
|
44
|
+
// >= 2 zero-pads to two digits (`hh` → "02"). Single-quoted text is emitted
|
|
45
|
+
// literally so the unit letters can appear as labels (`'h'` → "h"); `''` is a
|
|
46
|
+
// literal apostrophe. Any other character passes through unchanged.
|
|
47
|
+
//
|
|
48
|
+
// "h'h' m'm' s's'" → "1h 30m 45s"
|
|
49
|
+
// "hh:mm:ss" → "01:30:45"
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
function renderTemplate(
|
|
52
|
+
template: string,
|
|
53
|
+
hours: number,
|
|
54
|
+
minutes: number,
|
|
55
|
+
secs: number,
|
|
56
|
+
): string {
|
|
57
|
+
const pad = (value: number) => String(value).padStart(2, '0')
|
|
58
|
+
let out = ''
|
|
59
|
+
let i = 0
|
|
60
|
+
|
|
61
|
+
while (i < template.length) {
|
|
62
|
+
const ch = template[i]
|
|
63
|
+
|
|
64
|
+
if (ch === "'") {
|
|
65
|
+
i++
|
|
66
|
+
if (template[i] === "'") {
|
|
67
|
+
out += "'"
|
|
68
|
+
i++
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
while (i < template.length && template[i] !== "'") {
|
|
72
|
+
out += template[i]
|
|
73
|
+
i++
|
|
74
|
+
}
|
|
75
|
+
i++ // skip the closing quote
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (ch === 'h' || ch === 'm' || ch === 's') {
|
|
80
|
+
let len = 1
|
|
81
|
+
while (template[i + len] === ch) len++
|
|
82
|
+
const value = ch === 'h' ? hours : ch === 'm' ? minutes : secs
|
|
83
|
+
out += len >= 2 ? pad(value) : String(value)
|
|
84
|
+
i += len
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
out += ch
|
|
89
|
+
i++
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return out
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function formatShort(hours: number, minutes: number, secs: number): string {
|
|
96
|
+
const parts: string[] = []
|
|
97
|
+
if (hours) parts.push(`${hours}h`)
|
|
98
|
+
if (minutes) parts.push(`${minutes}m`)
|
|
99
|
+
if (secs) parts.push(`${secs}s`)
|
|
100
|
+
return parts.length ? parts.join(' ') : '0s'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function formatLong(hours: number, minutes: number, secs: number): string {
|
|
104
|
+
const parts: string[] = []
|
|
105
|
+
if (hours) parts.push(hours === 1 ? '1 hour' : `${hours} hours`)
|
|
106
|
+
if (minutes) parts.push(minutes === 1 ? '1 minute' : `${minutes} minutes`)
|
|
107
|
+
if (secs) parts.push(secs === 1 ? '1 second' : `${secs} seconds`)
|
|
108
|
+
return parts.length ? parts.join(' ') : '0 seconds'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function formatColon(hours: number, minutes: number, secs: number): string {
|
|
112
|
+
const pad = (value: number) => String(value).padStart(2, '0')
|
|
113
|
+
if (hours) return `${hours}:${pad(minutes)}:${pad(secs)}`
|
|
114
|
+
return `${minutes}:${pad(secs)}`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Parse: human-readable string → seconds (integer) or null if invalid.
|
|
119
|
+
//
|
|
120
|
+
// Supported formats (case-insensitive, spaces optional, any unit order):
|
|
121
|
+
// 1h 30m 45s | 4m 3h 4s | 4sec 3hour 4min | 1h30m45s
|
|
122
|
+
// 1:30:45 | 1:30 | :45
|
|
123
|
+
// 90 | 90s (bare integer treated as seconds)
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
export function parseDuration(str: string): number | null {
|
|
126
|
+
const input = str.trim().toLowerCase()
|
|
127
|
+
if (!input) return null
|
|
128
|
+
|
|
129
|
+
const colonSeconds = parseColon(input)
|
|
130
|
+
if (colonSeconds !== null) return colonSeconds
|
|
131
|
+
|
|
132
|
+
// Bare integer: treated as seconds.
|
|
133
|
+
if (/^\d+$/.test(input)) return parseInt(input, 10)
|
|
134
|
+
|
|
135
|
+
return parseUnits(input)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Colon-separated: h:m:s or m:s or :s. Groups are not bounded to 0-59:
|
|
139
|
+
// out-of-range parts overflow by design, so "1:75" parses to 135s (1m + 75s)
|
|
140
|
+
// and "5:99" to 399s. This mirrors stopwatch-style entry and is harmless.
|
|
141
|
+
function parseColon(input: string): number | null {
|
|
142
|
+
const match = input.match(/^(\d+):(\d+):(\d+)$|^(\d+):(\d+)$|^:(\d+)$/)
|
|
143
|
+
if (!match) return null
|
|
144
|
+
|
|
145
|
+
if (match[1] !== undefined) {
|
|
146
|
+
return (
|
|
147
|
+
parseInt(match[1], 10) * 3600 +
|
|
148
|
+
parseInt(match[2], 10) * 60 +
|
|
149
|
+
parseInt(match[3], 10)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
if (match[4] !== undefined) {
|
|
153
|
+
return parseInt(match[4], 10) * 60 + parseInt(match[5], 10)
|
|
154
|
+
}
|
|
155
|
+
return parseInt(match[6], 10)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Named units in any order: scan (number)(unit) tokens left-to-right and
|
|
159
|
+
// verify no unexpected text exists between tokens. Rejects duplicate units.
|
|
160
|
+
function parseUnits(input: string): number | null {
|
|
161
|
+
const tokenRe = /(\d+)\s*(hours?|hrs?|h|minutes?|mins?|m|seconds?|secs?|s)/g
|
|
162
|
+
const tokens: { value: number; unit: string }[] = []
|
|
163
|
+
let lastEnd = 0
|
|
164
|
+
let match: RegExpExecArray | null
|
|
165
|
+
|
|
166
|
+
while ((match = tokenRe.exec(input)) !== null) {
|
|
167
|
+
if (input.slice(lastEnd, match.index).trim() !== '') return null
|
|
168
|
+
tokens.push({ value: parseInt(match[1], 10), unit: match[2][0] })
|
|
169
|
+
lastEnd = match.index + match[0].length
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (input.slice(lastEnd).trim() !== '') return null
|
|
173
|
+
if (tokens.length === 0) return null
|
|
174
|
+
|
|
175
|
+
const seen = new Set<string>()
|
|
176
|
+
let total = 0
|
|
177
|
+
for (const { value, unit } of tokens) {
|
|
178
|
+
if (seen.has(unit)) return null
|
|
179
|
+
seen.add(unit)
|
|
180
|
+
if (unit === 'h') total += value * 3600
|
|
181
|
+
else if (unit === 'm') total += value * 60
|
|
182
|
+
else total += value
|
|
183
|
+
}
|
|
184
|
+
return total
|
|
185
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { Duration } from 'frappe-ui'
|
|
4
|
+
|
|
5
|
+
const value = ref<number | null>(7323)
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div class="flex flex-col gap-4 items-start">
|
|
10
|
+
<Duration v-model="value" format="short" label="short (default)" />
|
|
11
|
+
<Duration v-model="value" format="long" label="long" />
|
|
12
|
+
<Duration v-model="value" format="colon" label="colon" />
|
|
13
|
+
<Duration v-model="value" format="h'h' m'm' s's'" label="template: h'h' m'm' s's'" />
|
|
14
|
+
<Duration v-model="value" format="hh:mm:ss" label="template: hh:mm:ss" />
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import { Button, Duration, formatDuration } from 'frappe-ui'
|
|
4
|
+
|
|
5
|
+
// Logging effort against a CRM task: the user types a duration in any
|
|
6
|
+
// notation, "Log time" appends it, and the entries roll up into a total.
|
|
7
|
+
const entries = ref<{ label: string; seconds: number }[]>([
|
|
8
|
+
{ label: 'Discovery call', seconds: 2700 },
|
|
9
|
+
{ label: 'Proposal draft', seconds: 5445 },
|
|
10
|
+
])
|
|
11
|
+
|
|
12
|
+
const draft = ref<number | null>(null)
|
|
13
|
+
|
|
14
|
+
const total = computed(() =>
|
|
15
|
+
entries.value.reduce((sum, entry) => sum + entry.seconds, 0),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
function logTime() {
|
|
19
|
+
if (!draft.value) return
|
|
20
|
+
entries.value.push({ label: 'Follow-up', seconds: draft.value })
|
|
21
|
+
draft.value = null
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<div class="flex w-80 flex-col gap-4">
|
|
27
|
+
<div class="flex flex-col gap-2">
|
|
28
|
+
<div
|
|
29
|
+
v-for="(entry, index) in entries"
|
|
30
|
+
:key="index"
|
|
31
|
+
class="flex items-center justify-between text-base text-ink-gray-7"
|
|
32
|
+
>
|
|
33
|
+
<span>{{ entry.label }}</span>
|
|
34
|
+
<span class="text-ink-gray-9">{{ formatDuration(entry.seconds) }}</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="flex items-end gap-2">
|
|
39
|
+
<Duration v-model="draft" label="Log time" placeholder="e.g. 45m" />
|
|
40
|
+
<Button label="Log time" @click="logTime" />
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div
|
|
44
|
+
class="flex items-center justify-between border-t border-outline-gray-1 pt-3 text-base font-medium text-ink-gray-9"
|
|
45
|
+
>
|
|
46
|
+
<span>Total logged</span>
|
|
47
|
+
<span>{{ formatDuration(total, 'long') }}</span>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { Duration } from 'frappe-ui'
|
|
4
|
+
|
|
5
|
+
const value = ref<number | null>(5445)
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div class="flex flex-col gap-4 items-start">
|
|
10
|
+
<Duration v-model="value" size="sm" />
|
|
11
|
+
<Duration v-model="value" size="md" />
|
|
12
|
+
<Duration v-model="value" size="lg" />
|
|
13
|
+
<Duration v-model="value" size="xl" />
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Duration } from 'frappe-ui'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div class="flex flex-col gap-4 items-start">
|
|
7
|
+
<Duration label="Default" :model-value="5445" />
|
|
8
|
+
<Duration
|
|
9
|
+
label="With description"
|
|
10
|
+
description="Time spent on this task."
|
|
11
|
+
:model-value="5445"
|
|
12
|
+
/>
|
|
13
|
+
<Duration label="Required" required :model-value="5445" />
|
|
14
|
+
<Duration label="Disabled" disabled :model-value="5445" />
|
|
15
|
+
<Duration
|
|
16
|
+
label="With error"
|
|
17
|
+
error="Please enter a duration."
|
|
18
|
+
:model-value="5445"
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { InputSize, InputVariant } from '../../composables/inputTypes'
|
|
2
|
+
import type { InputLabelingProps } from '../../composables/useInputLabeling'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Named display presets (smart zero-omission; `long` also pluralizes):
|
|
6
|
+
* short — "1h 30m 45s"
|
|
7
|
+
* long — "1 hour 30 minutes 45 seconds"
|
|
8
|
+
* colon — "1:30:45"
|
|
9
|
+
*/
|
|
10
|
+
export type DurationFormatPreset = 'short' | 'long' | 'colon'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* How a duration is rendered: a named preset, or a token template string
|
|
14
|
+
* rendered literally — `h`/`hh`, `m`/`mm`, `s`/`ss`, with single-quoted text
|
|
15
|
+
* taken as a literal (e.g. `h'h' m'm' s's'` → "2h 2m 3s", `hh:mm:ss` → "02:02:03").
|
|
16
|
+
*/
|
|
17
|
+
// `string & {}` keeps preset autocomplete while still accepting any template.
|
|
18
|
+
export type DurationFormat = DurationFormatPreset | (string & {})
|
|
19
|
+
|
|
20
|
+
export interface DurationProps extends InputLabelingProps {
|
|
21
|
+
/** The duration value in seconds (two-way via `v-model`). */
|
|
22
|
+
modelValue?: number | null
|
|
23
|
+
|
|
24
|
+
/** Placeholder shown when the input is empty. Defaults to `1h 30m 45s`. */
|
|
25
|
+
placeholder?: string
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* How the saved value is rendered when not focused: a named preset
|
|
29
|
+
* (`short` | `long` | `colon`) or a token template (e.g. `h'h' m'm' s's'`,
|
|
30
|
+
* `hh:mm:ss`). Defaults to `short`.
|
|
31
|
+
*/
|
|
32
|
+
format?: DurationFormat
|
|
33
|
+
|
|
34
|
+
/** Visual size of the input. Forwarded to the underlying `TextInput`. */
|
|
35
|
+
size?: InputSize
|
|
36
|
+
|
|
37
|
+
/** Style variant of the input. Forwarded to the underlying `TextInput`. */
|
|
38
|
+
variant?: InputVariant
|
|
39
|
+
|
|
40
|
+
/** Disables the input when true. */
|
|
41
|
+
disabled?: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DurationEmits {
|
|
45
|
+
/** Fired when the model value changes. */
|
|
46
|
+
'update:modelValue': [value: number | null]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface DurationExposed {
|
|
50
|
+
/** Focuses the underlying text input. */
|
|
51
|
+
focus: () => void
|
|
52
|
+
}
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Displays a clear message to indicate errors or issues. Helps users understand problems and take corrective action quickly.
|
|
4
4
|
|
|
5
|
+
## Playground
|
|
6
|
+
|
|
7
|
+
<ClientOnly><ErrorMessageBuilder /></ClientOnly>
|
|
8
|
+
|
|
5
9
|
## String Message
|
|
6
10
|
<ComponentPreview name="ErrorMessage-Examples" />
|
|
7
11
|
|
|
@@ -6,27 +6,52 @@
|
|
|
6
6
|
|
|
7
7
|
const propsData = [
|
|
8
8
|
{
|
|
9
|
-
name: '
|
|
10
|
-
description: '',
|
|
9
|
+
name: 'fileTypes',
|
|
10
|
+
description: 'Accepted file types passed to the native file input.',
|
|
11
11
|
required: false,
|
|
12
|
-
type: '
|
|
13
|
-
default: 'null'
|
|
12
|
+
type: 'string | string[]'
|
|
14
13
|
},
|
|
15
14
|
{
|
|
16
|
-
name: '
|
|
17
|
-
description: '',
|
|
15
|
+
name: 'uploadArgs',
|
|
16
|
+
description: 'Additional upload options passed to Frappe\'s upload endpoint.\n`private` / `is_private` override the component\'s `isPrivate` default.',
|
|
18
17
|
required: false,
|
|
19
|
-
type: '
|
|
18
|
+
type: 'UploadOptions',
|
|
19
|
+
default: 'undefined'
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
name: '
|
|
23
|
-
description: '',
|
|
22
|
+
name: 'validateFile',
|
|
23
|
+
description: 'Optional validation hook. Return a message or Error to block upload.',
|
|
24
24
|
required: false,
|
|
25
|
-
type: '
|
|
25
|
+
type: '((file: File) => FileUploaderValidationResult | Promise<FileUploaderValidationResult>)',
|
|
26
|
+
default: 'undefined'
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const slotsData = [
|
|
31
|
+
{
|
|
32
|
+
name: 'default',
|
|
33
|
+
description: '',
|
|
34
|
+
type: 'FileUploaderSlotProps'
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
const emitsData = [
|
|
39
|
+
{
|
|
40
|
+
name: 'success',
|
|
41
|
+
description: '',
|
|
42
|
+
type: '[data: any]'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'failure',
|
|
46
|
+
description: '',
|
|
47
|
+
type: '[error: any]'
|
|
26
48
|
}
|
|
27
49
|
]
|
|
28
50
|
</script>
|
|
29
51
|
## API Reference
|
|
30
52
|
|
|
31
|
-
<PropsTable name="FileUploader" :data="propsData"/>
|
|
53
|
+
<PropsTable name="FileUploader" :data="propsData"/>
|
|
54
|
+
|
|
55
|
+
<SlotsTable :data="slotsData"/>
|
|
32
56
|
|
|
57
|
+
<EmitsTable :data="emitsData"/>
|