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
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
MenuGroupOption,
|
|
3
|
+
MenuItem,
|
|
4
|
+
MenuOption,
|
|
5
|
+
MenuOptions,
|
|
6
|
+
MenuTheme,
|
|
7
7
|
} from './types'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Group object after normalization. `options` is guaranteed to be present
|
|
11
11
|
* (the deprecated `items` alias has been resolved into it).
|
|
12
12
|
*/
|
|
13
|
-
export type
|
|
14
|
-
options:
|
|
13
|
+
export type NormalizedMenuGroup = MenuGroupOption & {
|
|
14
|
+
options: MenuOption[]
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export const
|
|
17
|
+
export const menuClasses = {
|
|
18
18
|
content:
|
|
19
|
-
'
|
|
19
|
+
'menu-content min-w-40 divide-y divide-outline-elevation-2 rounded-lg bg-surface-elevation-2 shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none',
|
|
20
20
|
group: 'p-1.5',
|
|
21
21
|
groupLabel: 'flex h-7 items-center px-2 text-sm font-medium text-ink-gray-4',
|
|
22
22
|
itemIcon: 'size-4 shrink-0',
|
|
@@ -26,45 +26,41 @@ export const dropdownClasses = {
|
|
|
26
26
|
'cursor-pointer rounded outline-none data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed',
|
|
27
27
|
} as const
|
|
28
28
|
|
|
29
|
-
export function
|
|
30
|
-
item: DropdownItem,
|
|
31
|
-
): item is DropdownGroupOption {
|
|
29
|
+
export function isMenuGroupOption(item: MenuItem): item is MenuGroupOption {
|
|
32
30
|
return 'group' in item && ('options' in item || 'items' in item)
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
function resolveGroupChildren(group:
|
|
33
|
+
function resolveGroupChildren(group: MenuGroupOption): MenuOption[] {
|
|
36
34
|
if (import.meta.env.DEV && group.options && group.items) {
|
|
37
35
|
console.warn(
|
|
38
|
-
'[
|
|
36
|
+
'[Menu] grouped entry has both `options` and `items`. `options` wins; `items` is the deprecated alias.',
|
|
39
37
|
)
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
return group.options ?? group.items ?? []
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
export function
|
|
43
|
+
export function isMenuSwitchOption(item: MenuOption) {
|
|
46
44
|
return item.switch === true
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
export function
|
|
47
|
+
export function isMenuSubmenuOption(item: MenuOption) {
|
|
50
48
|
return Array.isArray(item.submenu)
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
export function
|
|
51
|
+
export function isMenuComponentOption(item: MenuOption) {
|
|
54
52
|
return 'component' in item && Boolean(item.component)
|
|
55
53
|
}
|
|
56
54
|
|
|
57
|
-
function shouldRenderOption(option:
|
|
55
|
+
function shouldRenderOption(option: MenuOption) {
|
|
58
56
|
return option.condition ? option.condition() : true
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
function normalizeTheme(theme?:
|
|
59
|
+
function normalizeTheme(theme?: MenuTheme): MenuTheme {
|
|
62
60
|
return theme ?? 'gray'
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
export function
|
|
66
|
-
option: DropdownOption,
|
|
67
|
-
): DropdownOption {
|
|
63
|
+
export function normalizeMenuOption(option: MenuOption): MenuOption {
|
|
68
64
|
return {
|
|
69
65
|
...option,
|
|
70
66
|
theme: normalizeTheme(option.theme),
|
|
@@ -72,18 +68,18 @@ export function normalizeDropdownOption(
|
|
|
72
68
|
}
|
|
73
69
|
}
|
|
74
70
|
|
|
75
|
-
function normalizeGroupItems(items:
|
|
71
|
+
function normalizeGroupItems(items: MenuOption[]) {
|
|
76
72
|
return (items || [])
|
|
77
73
|
.filter(Boolean)
|
|
78
74
|
.filter(shouldRenderOption)
|
|
79
|
-
.map(
|
|
75
|
+
.map(normalizeMenuOption)
|
|
80
76
|
}
|
|
81
77
|
|
|
82
|
-
export function
|
|
83
|
-
options:
|
|
84
|
-
):
|
|
85
|
-
const groups:
|
|
86
|
-
let currentGroup:
|
|
78
|
+
export function normalizeMenuOptions(
|
|
79
|
+
options: MenuOptions = [],
|
|
80
|
+
): NormalizedMenuGroup[] {
|
|
81
|
+
const groups: NormalizedMenuGroup[] = []
|
|
82
|
+
let currentGroup: NormalizedMenuGroup | null = null
|
|
87
83
|
let implicitGroupIndex = 0
|
|
88
84
|
|
|
89
85
|
function flushCurrentGroup() {
|
|
@@ -99,7 +95,7 @@ export function normalizeDropdownOptions(
|
|
|
99
95
|
for (const [index, item] of options.entries()) {
|
|
100
96
|
if (!item) continue
|
|
101
97
|
|
|
102
|
-
if (
|
|
98
|
+
if (isMenuGroupOption(item)) {
|
|
103
99
|
flushCurrentGroup()
|
|
104
100
|
|
|
105
101
|
const visibleItems = normalizeGroupItems(resolveGroupChildren(item))
|
|
@@ -129,7 +125,7 @@ export function normalizeDropdownOptions(
|
|
|
129
125
|
}
|
|
130
126
|
}
|
|
131
127
|
|
|
132
|
-
currentGroup.options.push(
|
|
128
|
+
currentGroup.options.push(normalizeMenuOption(item))
|
|
133
129
|
}
|
|
134
130
|
|
|
135
131
|
flushCurrentGroup()
|
|
@@ -137,33 +133,33 @@ export function normalizeDropdownOptions(
|
|
|
137
133
|
return groups
|
|
138
134
|
}
|
|
139
135
|
|
|
140
|
-
export function groupHasIcons(group:
|
|
136
|
+
export function groupHasIcons(group: NormalizedMenuGroup) {
|
|
141
137
|
return group.options.some((item) => Boolean(item.icon))
|
|
142
138
|
}
|
|
143
139
|
|
|
144
|
-
export function
|
|
140
|
+
export function getMenuIconColor(item: {
|
|
145
141
|
disabled?: boolean
|
|
146
|
-
theme?:
|
|
142
|
+
theme?: MenuTheme
|
|
147
143
|
}) {
|
|
148
144
|
if (item.disabled) return 'text-ink-gray-4'
|
|
149
|
-
return item.theme === 'red' ? 'text-ink-red-
|
|
145
|
+
return item.theme === 'red' ? 'text-ink-red-6' : 'text-ink-gray-6'
|
|
150
146
|
}
|
|
151
147
|
|
|
152
|
-
export function
|
|
148
|
+
export function getMenuTextColor(item: {
|
|
153
149
|
disabled?: boolean
|
|
154
|
-
theme?:
|
|
150
|
+
theme?: MenuTheme
|
|
155
151
|
}) {
|
|
156
152
|
if (item.disabled) return 'text-ink-gray-4'
|
|
157
|
-
return item.theme === 'red' ? 'text-ink-red-
|
|
153
|
+
return item.theme === 'red' ? 'text-ink-red-6' : 'text-ink-gray-7'
|
|
158
154
|
}
|
|
159
155
|
|
|
160
|
-
export function
|
|
156
|
+
export function getMenuBackgroundColor(item: { theme?: MenuTheme }) {
|
|
161
157
|
if (item.theme === 'red') {
|
|
162
158
|
return 'focus:bg-surface-red-3 data-[highlighted]:bg-surface-red-3 data-[state=open]:bg-surface-red-3'
|
|
163
159
|
}
|
|
164
160
|
|
|
165
161
|
return [
|
|
166
|
-
'focus:bg-surface-gray-2 data-[highlighted]:bg-surface-gray-2 data-[state=open]:bg-surface-gray-2',
|
|
162
|
+
'focus:bg-surface-gray-2 data-[highlighted]:bg-surface-alpha-gray-2 data-[state=open]:bg-surface-gray-2',
|
|
167
163
|
'data-[state=checked]:bg-surface-gray-3',
|
|
168
164
|
'data-[highlighted]:data-[state=checked]:bg-surface-gray-4',
|
|
169
165
|
].join(' ')
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<!-- Auto Generated by scripts/propsgen.ts -->
|
|
2
|
+
<script setup>
|
|
3
|
+
import PropsTable from '@/components/Docs/PropsTable.vue'
|
|
4
|
+
import SlotsTable from '@/components/Docs/SlotsTable.vue'
|
|
5
|
+
import EmitsTable from '@/components/Docs/EmitsTable.vue'
|
|
6
|
+
|
|
7
|
+
const mobileNavSlots = [
|
|
8
|
+
{
|
|
9
|
+
name: 'default',
|
|
10
|
+
description: '',
|
|
11
|
+
type: '{}'
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const mobileNavItemProps = [
|
|
16
|
+
{
|
|
17
|
+
name: 'label',
|
|
18
|
+
description: 'Label under the icon; also the accessible name.',
|
|
19
|
+
required: true,
|
|
20
|
+
type: 'string'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'icon',
|
|
24
|
+
description: 'Icon CSS class, e.g. `lucide-home`, or a component. Ignored when the\ndefault slot is used (for a custom glyph or an avatar).',
|
|
25
|
+
required: false,
|
|
26
|
+
type: 'string | Component'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'to',
|
|
30
|
+
description: 'Navigation target. Renders a router link. Tapping the item while it is\nalready the current route scrolls the shell to the top instead of\nre-navigating.',
|
|
31
|
+
required: false,
|
|
32
|
+
type: 'string | kt | Tt'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'active',
|
|
36
|
+
description: 'Highlight this item. Independent of the current route so one tab can stay\nlit across a whole section (e.g. Home across community routes). Defaults to\nwhether `to` resolves to the current route.',
|
|
37
|
+
required: false,
|
|
38
|
+
type: 'boolean'
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
const mobileNavItemSlots = [
|
|
43
|
+
{
|
|
44
|
+
name: 'default',
|
|
45
|
+
description: '',
|
|
46
|
+
type: '{ active: boolean; }'
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
const mobileNavItemEmits = [
|
|
51
|
+
{
|
|
52
|
+
name: 'click',
|
|
53
|
+
description: '',
|
|
54
|
+
type: '[event: MouseEvent]'
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
</script>
|
|
58
|
+
## API Reference
|
|
59
|
+
|
|
60
|
+
### MobileNav
|
|
61
|
+
|
|
62
|
+
<SlotsTable :data="mobileNavSlots"/>
|
|
63
|
+
|
|
64
|
+
### MobileNavItem
|
|
65
|
+
|
|
66
|
+
<PropsTable folder="MobileNav" name="MobileNavItem" :data="mobileNavItemProps"/>
|
|
67
|
+
|
|
68
|
+
<SlotsTable :data="mobileNavItemSlots"/>
|
|
69
|
+
|
|
70
|
+
<EmitsTable :data="mobileNavItemEmits"/>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { h } from 'vue'
|
|
2
|
+
import { createMemoryHistory, createRouter } from 'vue-router'
|
|
3
|
+
import MobileNav from './MobileNav.vue'
|
|
4
|
+
import MobileNavItem from './MobileNavItem.vue'
|
|
5
|
+
import {
|
|
6
|
+
registerScrollContainer,
|
|
7
|
+
unregisterScrollContainer,
|
|
8
|
+
} from '../../composables/useScrollContainer'
|
|
9
|
+
|
|
10
|
+
function routerAt(path: string) {
|
|
11
|
+
const router = createRouter({
|
|
12
|
+
history: createMemoryHistory(),
|
|
13
|
+
routes: [
|
|
14
|
+
{ path: '/', name: 'home', component: { template: '<div />' } },
|
|
15
|
+
{ path: '/search', name: 'search', component: { template: '<div />' } },
|
|
16
|
+
],
|
|
17
|
+
})
|
|
18
|
+
router.push(path)
|
|
19
|
+
return router
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('<MobileNav />', () => {
|
|
23
|
+
it('lays its items out as an equal-width grid', () => {
|
|
24
|
+
cy.mount(MobileNav, {
|
|
25
|
+
slots: {
|
|
26
|
+
default: () => [
|
|
27
|
+
h(MobileNavItem, { label: 'Home', icon: 'lucide-house' }),
|
|
28
|
+
h(MobileNavItem, { label: 'Search', icon: 'lucide-search' }),
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
cy.get('[data-slot=mobile-nav]').should('have.class', 'grid')
|
|
33
|
+
cy.get('[data-slot=mobile-nav-item]').should('have.length', 2)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('<MobileNavItem />', () => {
|
|
38
|
+
it('renders a button and emits click when there is no `to`', () => {
|
|
39
|
+
const onClick = cy.stub().as('click')
|
|
40
|
+
cy.mount(MobileNavItem, { props: { label: 'Home', icon: 'lucide-house', onClick } })
|
|
41
|
+
cy.get('button[data-slot=mobile-nav-item]').should('exist')
|
|
42
|
+
cy.get('button').click()
|
|
43
|
+
cy.get('@click').should('have.been.calledOnce')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('renders a router link to a different route', () => {
|
|
47
|
+
cy.mount(MobileNavItem, {
|
|
48
|
+
props: { label: 'Search', icon: 'lucide-search', to: '/search' },
|
|
49
|
+
global: { plugins: [routerAt('/')] },
|
|
50
|
+
})
|
|
51
|
+
cy.get('a[data-slot=mobile-nav-item]').should('have.attr', 'href', '/search')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('is a button (not a link) when `to` is already the current route', () => {
|
|
55
|
+
cy.mount(MobileNavItem, {
|
|
56
|
+
props: { label: 'Home', icon: 'lucide-house', to: '/' },
|
|
57
|
+
global: { plugins: [routerAt('/')] },
|
|
58
|
+
})
|
|
59
|
+
cy.get('button[data-slot=mobile-nav-item]').should('exist')
|
|
60
|
+
cy.get('a[data-slot=mobile-nav-item]').should('not.exist')
|
|
61
|
+
cy.get('[data-slot=mobile-nav-item]').should('have.attr', 'aria-current', 'page')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('scrolls the registered container to the top when the current tab is tapped', () => {
|
|
65
|
+
const scroller = document.createElement('div')
|
|
66
|
+
Object.defineProperty(scroller, 'scrollHeight', { value: 2000 })
|
|
67
|
+
const scrollTo = cy.stub().as('scrollTo')
|
|
68
|
+
scroller.scrollTo = scrollTo
|
|
69
|
+
registerScrollContainer(scroller)
|
|
70
|
+
|
|
71
|
+
cy.mount(MobileNavItem, {
|
|
72
|
+
props: { label: 'Home', icon: 'lucide-house', to: '/' },
|
|
73
|
+
global: { plugins: [routerAt('/')] },
|
|
74
|
+
})
|
|
75
|
+
cy.get('button[data-slot=mobile-nav-item]').click()
|
|
76
|
+
cy.get('@scrollTo')
|
|
77
|
+
.should('have.been.calledOnce')
|
|
78
|
+
.then(() => unregisterScrollContainer(scroller))
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('reflects `active` in data-state and exposes it to the default slot', () => {
|
|
82
|
+
cy.mount(MobileNavItem, {
|
|
83
|
+
props: { label: 'You', active: true },
|
|
84
|
+
slots: { default: (props: { active: boolean }) => h('span', `active:${props.active}`) },
|
|
85
|
+
})
|
|
86
|
+
cy.get('[data-slot=mobile-nav-item][data-state=active]').should('exist')
|
|
87
|
+
cy.get('[data-slot=mobile-nav-item]').should('contain.text', 'active:true')
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# MobileNav
|
|
2
|
+
|
|
3
|
+
The bottom tab bar for a [`MobileShell`](/docs/components/mobileshell).
|
|
4
|
+
`MobileNav` is a grid frame; each `MobileNavItem` becomes one equal-width tab, so
|
|
5
|
+
the bar adapts to any number of items.
|
|
6
|
+
|
|
7
|
+
<ComponentPreview name="MobileNav-Default" />
|
|
8
|
+
|
|
9
|
+
## MobileNavItem
|
|
10
|
+
|
|
11
|
+
Each item takes a `label`, an `icon` (or a default slot for custom content like
|
|
12
|
+
an avatar), and a `to` target. It renders a router link when navigating
|
|
13
|
+
somewhere new, and — when it's already the current route — a button that scrolls
|
|
14
|
+
the shell's scroll container to the top instead of re-navigating.
|
|
15
|
+
|
|
16
|
+
`active` controls the highlight and is independent of the current route, so one
|
|
17
|
+
tab can stay lit across a whole section (e.g. Home across every community route)
|
|
18
|
+
while tapping it still navigates home. When `active` is omitted it defaults to
|
|
19
|
+
whether `to` matches the current route. The default slot receives `{ active }`
|
|
20
|
+
so custom content — an avatar, a badge — can react to the highlight:
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<MobileNavItem label="You" :to="{ name: 'More' }" :active="isMoreRoute">
|
|
24
|
+
<template #default="{ active }">
|
|
25
|
+
<UserAvatar :user="me" :class="{ 'ring-2 ring-outline-gray-4': active }" />
|
|
26
|
+
</template>
|
|
27
|
+
</MobileNavItem>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Like `SidebarItem`, `MobileNavItem` is router-optional: mounted without
|
|
31
|
+
vue-router it degrades to a plain `<a>`/`<button>` with no warnings, so it works
|
|
32
|
+
in docs, tests, and embedded use.
|
|
33
|
+
|
|
34
|
+
<!-- @include: ./MobileNav.api.md -->
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<nav
|
|
3
|
+
data-slot="mobile-nav"
|
|
4
|
+
class="grid shrink-0 auto-cols-fr grid-flow-col border-t border-outline-gray-2 bg-surface-elevation-2 [@media(display-mode:standalone)]:pb-4"
|
|
5
|
+
>
|
|
6
|
+
<slot />
|
|
7
|
+
</nav>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
// A bare grid frame: each MobileNavItem becomes one equal-width column
|
|
12
|
+
// (auto-cols-fr + grid-flow-col), so the bar adapts to any item count without
|
|
13
|
+
// hardcoding a column class. Bottom padding clears the home indicator in an
|
|
14
|
+
// installed PWA (display-mode: standalone).
|
|
15
|
+
</script>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!--
|
|
3
|
+
A real link when navigating somewhere new; a plain button when this item is
|
|
4
|
+
already the current route (tap = scroll to top) or has no target. Switching
|
|
5
|
+
the element on `isCurrent` — rather than preventing RouterLink's default —
|
|
6
|
+
keeps the scroll-vs-navigate decision unambiguous. The 'button' string can't
|
|
7
|
+
go through <component :is> (it collides with the global <Button>), so the
|
|
8
|
+
button stays a literal v-else; the inner content is shared.
|
|
9
|
+
-->
|
|
10
|
+
<component
|
|
11
|
+
:is="linkComponent"
|
|
12
|
+
v-if="to && !isCurrent"
|
|
13
|
+
v-bind="linkAttrs"
|
|
14
|
+
data-slot="mobile-nav-item"
|
|
15
|
+
:data-state="resolvedActive ? 'active' : 'inactive'"
|
|
16
|
+
:aria-label="label"
|
|
17
|
+
:aria-current="isCurrent ? 'page' : undefined"
|
|
18
|
+
class="flex min-h-14 flex-col items-center justify-center gap-1 py-2 transition active:scale-95"
|
|
19
|
+
@click="emit('click', $event)"
|
|
20
|
+
>
|
|
21
|
+
<slot :active="resolvedActive">
|
|
22
|
+
<span
|
|
23
|
+
v-if="typeof icon === 'string'"
|
|
24
|
+
:class="[icon, resolvedActive ? 'text-ink-gray-8' : 'text-ink-gray-5']"
|
|
25
|
+
class="size-6"
|
|
26
|
+
aria-hidden="true"
|
|
27
|
+
/>
|
|
28
|
+
<component
|
|
29
|
+
v-else-if="icon"
|
|
30
|
+
:is="icon"
|
|
31
|
+
:class="resolvedActive ? 'text-ink-gray-8' : 'text-ink-gray-5'"
|
|
32
|
+
class="size-6"
|
|
33
|
+
aria-hidden="true"
|
|
34
|
+
/>
|
|
35
|
+
</slot>
|
|
36
|
+
<span
|
|
37
|
+
class="text-xs-medium"
|
|
38
|
+
:class="resolvedActive ? 'text-ink-gray-8' : 'text-ink-gray-5'"
|
|
39
|
+
>
|
|
40
|
+
{{ label }}
|
|
41
|
+
</span>
|
|
42
|
+
</component>
|
|
43
|
+
|
|
44
|
+
<button
|
|
45
|
+
v-else
|
|
46
|
+
type="button"
|
|
47
|
+
data-slot="mobile-nav-item"
|
|
48
|
+
:data-state="resolvedActive ? 'active' : 'inactive'"
|
|
49
|
+
:aria-label="label"
|
|
50
|
+
:aria-current="isCurrent ? 'page' : undefined"
|
|
51
|
+
class="flex min-h-14 flex-col items-center justify-center gap-1 py-2 transition active:scale-95"
|
|
52
|
+
@click="onButtonClick"
|
|
53
|
+
>
|
|
54
|
+
<slot :active="resolvedActive">
|
|
55
|
+
<span
|
|
56
|
+
v-if="typeof icon === 'string'"
|
|
57
|
+
:class="[icon, resolvedActive ? 'text-ink-gray-8' : 'text-ink-gray-5']"
|
|
58
|
+
class="size-6"
|
|
59
|
+
aria-hidden="true"
|
|
60
|
+
/>
|
|
61
|
+
<component
|
|
62
|
+
v-else-if="icon"
|
|
63
|
+
:is="icon"
|
|
64
|
+
:class="resolvedActive ? 'text-ink-gray-8' : 'text-ink-gray-5'"
|
|
65
|
+
class="size-6"
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
/>
|
|
68
|
+
</slot>
|
|
69
|
+
<span
|
|
70
|
+
class="text-xs-medium"
|
|
71
|
+
:class="resolvedActive ? 'text-ink-gray-8' : 'text-ink-gray-5'"
|
|
72
|
+
>
|
|
73
|
+
{{ label }}
|
|
74
|
+
</span>
|
|
75
|
+
</button>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<script setup lang="ts">
|
|
79
|
+
import { computed, getCurrentInstance } from 'vue'
|
|
80
|
+
import { RouterLink } from 'vue-router'
|
|
81
|
+
import { scrollToTop } from '../../composables/useScrollContainer'
|
|
82
|
+
import type { MobileNavItemProps } from './types'
|
|
83
|
+
|
|
84
|
+
const props = defineProps<MobileNavItemProps>()
|
|
85
|
+
const emit = defineEmits<{ click: [event: MouseEvent] }>()
|
|
86
|
+
|
|
87
|
+
// Read router/route off global properties (not useRouter/useRoute) so the item
|
|
88
|
+
// works without warnings when mounted outside a vue-router app — docs, tests,
|
|
89
|
+
// embedded use. Same approach as SidebarItem.
|
|
90
|
+
const globals = getCurrentInstance()?.appContext.config.globalProperties
|
|
91
|
+
const hasRouter = computed(() => Boolean(globals?.$router))
|
|
92
|
+
|
|
93
|
+
const linkComponent = computed(() => (hasRouter.value ? RouterLink : 'a'))
|
|
94
|
+
const linkAttrs = computed(() =>
|
|
95
|
+
hasRouter.value
|
|
96
|
+
? { to: props.to }
|
|
97
|
+
: { href: typeof props.to === 'string' ? props.to : undefined },
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const resolvedRoute = computed(() =>
|
|
101
|
+
props.to && globals?.$router ? globals.$router.resolve(props.to) : null,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
// Is this item's target the exact current route? Drives scroll-vs-navigate and
|
|
105
|
+
// aria-current — distinct from `active`, which may span a whole section.
|
|
106
|
+
const isCurrent = computed(() => {
|
|
107
|
+
const target = resolvedRoute.value
|
|
108
|
+
const current = globals?.$route
|
|
109
|
+
if (!target || !current) return false
|
|
110
|
+
return target.name
|
|
111
|
+
? current.name === target.name
|
|
112
|
+
: current.path === target.path
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// Explicit `active` wins (broad section highlight); otherwise fall back to the
|
|
116
|
+
// current-route match so simple navs light up the current item automatically.
|
|
117
|
+
const resolvedActive = computed(() => props.active ?? isCurrent.value)
|
|
118
|
+
|
|
119
|
+
function onButtonClick(event: MouseEvent) {
|
|
120
|
+
// Reached only when this item is already current (or has no target): a tap
|
|
121
|
+
// scrolls the shell's scroll container to the top.
|
|
122
|
+
if (isCurrent.value) scrollToTop()
|
|
123
|
+
emit('click', event)
|
|
124
|
+
}
|
|
125
|
+
</script>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { MobileNav, MobileNavItem, Avatar } from 'frappe-ui'
|
|
4
|
+
|
|
5
|
+
const tab = ref('home')
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div class="mx-auto w-[360px] overflow-hidden rounded-xl border bg-surface-white">
|
|
10
|
+
<MobileNav>
|
|
11
|
+
<MobileNavItem
|
|
12
|
+
label="Home"
|
|
13
|
+
icon="lucide-house"
|
|
14
|
+
:active="tab === 'home'"
|
|
15
|
+
@click="tab = 'home'"
|
|
16
|
+
/>
|
|
17
|
+
<MobileNavItem
|
|
18
|
+
label="Notifications"
|
|
19
|
+
icon="lucide-bell"
|
|
20
|
+
:active="tab === 'notifications'"
|
|
21
|
+
@click="tab = 'notifications'"
|
|
22
|
+
/>
|
|
23
|
+
<MobileNavItem
|
|
24
|
+
label="Search"
|
|
25
|
+
icon="lucide-search"
|
|
26
|
+
:active="tab === 'search'"
|
|
27
|
+
@click="tab = 'search'"
|
|
28
|
+
/>
|
|
29
|
+
<MobileNavItem label="You" :active="tab === 'you'" @click="tab = 'you'">
|
|
30
|
+
<template #default="{ active }">
|
|
31
|
+
<Avatar
|
|
32
|
+
label="Jane Doe"
|
|
33
|
+
size="md"
|
|
34
|
+
:class="active ? 'ring-2 ring-outline-gray-4' : ''"
|
|
35
|
+
/>
|
|
36
|
+
</template>
|
|
37
|
+
</MobileNavItem>
|
|
38
|
+
</MobileNav>
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Component } from 'vue'
|
|
2
|
+
import type { RouteLocationRaw } from 'vue-router'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `MobileNav` (the bar) has no props — it's a grid frame for `MobileNavItem`s.
|
|
6
|
+
*/
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
8
|
+
export interface MobileNavProps {}
|
|
9
|
+
|
|
10
|
+
export interface MobileNavItemProps {
|
|
11
|
+
/** Label under the icon; also the accessible name. */
|
|
12
|
+
label: string
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Icon CSS class, e.g. `lucide-home`, or a component. Ignored when the
|
|
16
|
+
* default slot is used (for a custom glyph or an avatar).
|
|
17
|
+
*/
|
|
18
|
+
icon?: string | Component
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Navigation target. Renders a router link. Tapping the item while it is
|
|
22
|
+
* already the current route scrolls the shell to the top instead of
|
|
23
|
+
* re-navigating.
|
|
24
|
+
*/
|
|
25
|
+
to?: RouteLocationRaw
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Highlight this item. Independent of the current route so one tab can stay
|
|
29
|
+
* lit across a whole section (e.g. Home across community routes). Defaults to
|
|
30
|
+
* whether `to` resolves to the current route.
|
|
31
|
+
*/
|
|
32
|
+
active?: boolean
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!-- Auto Generated by scripts/propsgen.ts -->
|
|
2
|
+
<script setup>
|
|
3
|
+
import PropsTable from '@/components/Docs/PropsTable.vue'
|
|
4
|
+
import SlotsTable from '@/components/Docs/SlotsTable.vue'
|
|
5
|
+
import EmitsTable from '@/components/Docs/EmitsTable.vue'
|
|
6
|
+
|
|
7
|
+
const slotsData = [
|
|
8
|
+
{
|
|
9
|
+
name: 'default',
|
|
10
|
+
description: '',
|
|
11
|
+
type: '{}'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'nav',
|
|
15
|
+
description: '',
|
|
16
|
+
type: '{}'
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
</script>
|
|
20
|
+
## API Reference
|
|
21
|
+
|
|
22
|
+
<SlotsTable :data="slotsData"/>
|
|
23
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { h } from 'vue'
|
|
2
|
+
import MobileShell from './MobileShell.vue'
|
|
3
|
+
import { getScrollContainer } from '../../composables/useScrollContainer'
|
|
4
|
+
|
|
5
|
+
describe('<MobileShell />', () => {
|
|
6
|
+
it('renders the default content and #nav slots', () => {
|
|
7
|
+
cy.mount(MobileShell, {
|
|
8
|
+
slots: {
|
|
9
|
+
default: () => h('div', { 'data-test': 'page' }, 'page body'),
|
|
10
|
+
nav: () => h('div', { 'data-test': 'nav' }, 'tab bar'),
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
cy.get('[data-slot=mobile-shell]').should('exist')
|
|
14
|
+
cy.get('[data-slot=mobile-shell-scroll]').should('contain.text', 'page body')
|
|
15
|
+
cy.get('[data-test=nav]').should('contain.text', 'tab bar')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('registers its scroll region so getScrollContainer() resolves', () => {
|
|
19
|
+
cy.mount(MobileShell, { slots: { default: () => h('div', 'content') } })
|
|
20
|
+
cy.get('[data-slot=mobile-shell-scroll]').should('exist')
|
|
21
|
+
cy.then(() => {
|
|
22
|
+
expect(getScrollContainer()).to.not.be.null
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# MobileShell
|
|
2
|
+
|
|
3
|
+
The mobile app frame. `MobileShell` is a fixed, full-height column: a pinned
|
|
4
|
+
`PageHeaderTarget` on top (with safe-area padding for installed PWAs), a
|
|
5
|
+
native-scrolling content area, and a `#nav` slot for a bottom
|
|
6
|
+
[`MobileNav`](/docs/components/mobilenav). It's a separate family from
|
|
7
|
+
[`DesktopShell`](/docs/components/desktopshell) — mobile and desktop are
|
|
8
|
+
different navigation models, so the app chooses which to render for the viewport
|
|
9
|
+
rather than toggling one responsive component.
|
|
10
|
+
|
|
11
|
+
<ComponentPreview name="MobileShell-Default" />
|
|
12
|
+
|
|
13
|
+
Put the routed page in the default slot and the tab bar in `#nav`. The content
|
|
14
|
+
area scrolls with the platform's own momentum and overscroll; the header and nav
|
|
15
|
+
stay fixed to the edges.
|
|
16
|
+
|
|
17
|
+
## Scroll container
|
|
18
|
+
|
|
19
|
+
Like `DesktopShell`, the content area registers into the scroll-container
|
|
20
|
+
registry, so [`useScrollContainer()`](/docs/other/utilities) and
|
|
21
|
+
`getScrollContainer()` resolve it — a tapped-active tab or a router
|
|
22
|
+
`scrollBehavior` can drive it with no app-owned global.
|
|
23
|
+
|
|
24
|
+
<!-- @include: ./MobileShell.api.md -->
|