@umbra.ui/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/controls/Dropdown/types.d.ts +5 -0
- package/dist/components/controls/Dropdown/types.d.ts.map +1 -0
- package/dist/components/controls/Dropdown/types.js +1 -0
- package/dist/components/controls/SegmentedControl/types.d.ts +6 -0
- package/dist/components/controls/SegmentedControl/types.d.ts.map +1 -0
- package/dist/components/controls/SegmentedControl/types.js +1 -0
- package/dist/components/dialogs/Alert/types.d.ts +7 -0
- package/dist/components/dialogs/Alert/types.d.ts.map +1 -0
- package/dist/components/dialogs/Alert/types.js +1 -0
- package/dist/components/dialogs/Toast/types.d.ts +34 -0
- package/dist/components/dialogs/Toast/types.d.ts.map +1 -0
- package/dist/components/dialogs/Toast/types.js +10 -0
- package/dist/components/dialogs/Toast/useToast.d.ts +36 -0
- package/dist/components/dialogs/Toast/useToast.d.ts.map +1 -0
- package/dist/components/dialogs/Toast/useToast.js +90 -0
- package/dist/components/indicators/Tooltip/tooltip.d.ts +3 -0
- package/dist/components/indicators/Tooltip/tooltip.d.ts.map +1 -0
- package/dist/components/indicators/Tooltip/tooltip.js +33 -0
- package/dist/components/indicators/Tooltip/types.d.ts +14 -0
- package/dist/components/indicators/Tooltip/types.d.ts.map +1 -0
- package/dist/components/indicators/Tooltip/types.js +1 -0
- package/dist/components/indicators/Tooltip/useTooltip.d.ts +18 -0
- package/dist/components/indicators/Tooltip/useTooltip.d.ts.map +1 -0
- package/dist/components/indicators/Tooltip/useTooltip.js +57 -0
- package/dist/components/inputs/Tags/tag-bar-styles.d.ts +14 -0
- package/dist/components/inputs/Tags/tag-bar-styles.d.ts.map +1 -0
- package/dist/components/inputs/Tags/tag-bar-styles.js +313 -0
- package/dist/components/inputs/Tags/types.d.ts +93 -0
- package/dist/components/inputs/Tags/types.d.ts.map +1 -0
- package/dist/components/inputs/Tags/types.js +216 -0
- package/dist/components/inputs/search/types.d.ts +9 -0
- package/dist/components/inputs/search/types.d.ts.map +1 -0
- package/dist/components/inputs/search/types.js +1 -0
- package/dist/components/navigation/adaptive/types.d.ts +16 -0
- package/dist/components/navigation/adaptive/types.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/types.js +1 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts +27 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.js +40 -0
- package/dist/components/navigation/adaptive/useBreakpoints.d.ts +6 -0
- package/dist/components/navigation/adaptive/useBreakpoints.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useBreakpoints.js +37 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.d.ts +93 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.js +145 -0
- package/dist/components/navigation/adaptive/useViewAnimation.d.ts +31 -0
- package/dist/components/navigation/adaptive/useViewAnimation.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useViewAnimation.js +591 -0
- package/dist/components/navigation/adaptive/useViewResize.d.ts +52 -0
- package/dist/components/navigation/adaptive/useViewResize.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useViewResize.js +146 -0
- package/dist/components/navigation/navstack/useNavigationStack.d.ts +25 -0
- package/dist/components/navigation/navstack/useNavigationStack.d.ts.map +1 -0
- package/dist/components/navigation/navstack/useNavigationStack.js +133 -0
- package/dist/components/navigation/slideover/useSlideoverController.d.ts +20 -0
- package/dist/components/navigation/slideover/useSlideoverController.d.ts.map +1 -0
- package/dist/components/navigation/slideover/useSlideoverController.js +267 -0
- package/dist/components/navigation/splitview/useSplitViewController.d.ts +20 -0
- package/dist/components/navigation/splitview/useSplitViewController.d.ts.map +1 -0
- package/dist/components/navigation/splitview/useSplitViewController.js +325 -0
- package/dist/components/navigation/tabcontroller/types.d.ts +21 -0
- package/dist/components/navigation/tabcontroller/types.d.ts.map +1 -0
- package/dist/components/navigation/tabcontroller/types.js +1 -0
- package/dist/components/navigation/tabcontroller/useTabController.d.ts +5 -0
- package/dist/components/navigation/tabcontroller/useTabController.d.ts.map +1 -0
- package/dist/components/navigation/tabcontroller/useTabController.js +10 -0
- package/dist/components/navigation/types.d.ts +8 -0
- package/dist/components/navigation/types.d.ts.map +1 -0
- package/dist/components/navigation/types.js +1 -0
- package/dist/components/pickers/CollectionPicker/types.d.ts +11 -0
- package/dist/components/pickers/CollectionPicker/types.d.ts.map +1 -0
- package/dist/components/pickers/CollectionPicker/types.js +1 -0
- package/dist/components/pickers/ColorPicker/colors.d.ts +13 -0
- package/dist/components/pickers/ColorPicker/colors.d.ts.map +1 -0
- package/dist/components/pickers/ColorPicker/colors.js +266 -0
- package/dist/components/pickers/FilePicker/types.d.ts +10 -0
- package/dist/components/pickers/FilePicker/types.d.ts.map +1 -0
- package/dist/components/pickers/FilePicker/types.js +1 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +196 -0
- package/dist/theme.d.ts +73 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +279 -0
- package/dist/themes/blank.d.ts +7 -0
- package/dist/themes/blank.d.ts.map +1 -0
- package/dist/themes/blank.js +543 -0
- package/dist/themes/crimson-dark.d.ts +4 -0
- package/dist/themes/crimson-dark.d.ts.map +1 -0
- package/dist/themes/crimson-dark.js +552 -0
- package/dist/themes/cyan-light.d.ts +4 -0
- package/dist/themes/cyan-light.d.ts.map +1 -0
- package/dist/themes/cyan-light.js +552 -0
- package/dist/themes/dark.d.ts +4 -0
- package/dist/themes/dark.d.ts.map +1 -0
- package/dist/themes/dark.js +551 -0
- package/dist/themes/gold-dark.d.ts +4 -0
- package/dist/themes/gold-dark.d.ts.map +1 -0
- package/dist/themes/gold-dark.js +552 -0
- package/dist/themes/grass-dark.d.ts +4 -0
- package/dist/themes/grass-dark.d.ts.map +1 -0
- package/dist/themes/grass-dark.js +552 -0
- package/dist/themes/indigo.d.ts +4 -0
- package/dist/themes/indigo.d.ts.map +1 -0
- package/dist/themes/indigo.js +552 -0
- package/dist/themes/light.d.ts +4 -0
- package/dist/themes/light.d.ts.map +1 -0
- package/dist/themes/light.js +551 -0
- package/dist/themes/orange-dark.d.ts +4 -0
- package/dist/themes/orange-dark.d.ts.map +1 -0
- package/dist/themes/orange-dark.js +551 -0
- package/dist/themes/orange-light.d.ts +4 -0
- package/dist/themes/orange-light.d.ts.map +1 -0
- package/dist/themes/orange-light.js +551 -0
- package/package.json +62 -0
- package/src/components/controls/Button/Button.vue +417 -0
- package/src/components/controls/Button/README.md +348 -0
- package/src/components/controls/Button/theme.css +200 -0
- package/src/components/controls/Checkbox/Checkbox.vue +164 -0
- package/src/components/controls/Checkbox/README.md +441 -0
- package/src/components/controls/Checkbox/theme.css +36 -0
- package/src/components/controls/Dropdown/Dropdown.vue +476 -0
- package/src/components/controls/Dropdown/README.md +370 -0
- package/src/components/controls/Dropdown/theme.css +50 -0
- package/src/components/controls/Dropdown/types.ts +6 -0
- package/src/components/controls/IconButton/IconButton.vue +267 -0
- package/src/components/controls/IconButton/README.md +502 -0
- package/src/components/controls/IconButton/theme.css +89 -0
- package/src/components/controls/Radio/README.md +591 -0
- package/src/components/controls/Radio/Radio.vue +89 -0
- package/src/components/controls/Radio/theme.css +14 -0
- package/src/components/controls/RangeSlider/README.md +608 -0
- package/src/components/controls/RangeSlider/RangeSlider.vue +535 -0
- package/src/components/controls/RangeSlider/theme.css +80 -0
- package/src/components/controls/SegmentedControl/README.md +587 -0
- package/src/components/controls/SegmentedControl/SegmentedControl.vue +284 -0
- package/src/components/controls/SegmentedControl/theme.css +60 -0
- package/src/components/controls/SegmentedControl/types.ts +5 -0
- package/src/components/controls/Slider/README.md +627 -0
- package/src/components/controls/Slider/Slider.vue +260 -0
- package/src/components/controls/Slider/theme.css +74 -0
- package/src/components/controls/Stepper/README.md +601 -0
- package/src/components/controls/Stepper/Stepper.vue +103 -0
- package/src/components/controls/Stepper/theme.css +53 -0
- package/src/components/controls/Switch/README.md +667 -0
- package/src/components/controls/Switch/Switch.vue +127 -0
- package/src/components/controls/Switch/theme.css +42 -0
- package/src/components/dialogs/Alert/Alert.vue +218 -0
- package/src/components/dialogs/Alert/README.md +450 -0
- package/src/components/dialogs/Alert/theme.css +44 -0
- package/src/components/dialogs/Alert/types.ts +11 -0
- package/src/components/dialogs/Toast/README.md +522 -0
- package/src/components/dialogs/Toast/Toast.vue +296 -0
- package/src/components/dialogs/Toast/ToastContainer.vue +330 -0
- package/src/components/dialogs/Toast/theme.css +44 -0
- package/src/components/dialogs/Toast/types.ts +46 -0
- package/src/components/dialogs/Toast/useToast.ts +127 -0
- package/src/components/indicators/ProgressBar/ProgressBar.vue +98 -0
- package/src/components/indicators/ProgressBar/README.md +744 -0
- package/src/components/indicators/ProgressBar/theme.css +36 -0
- package/src/components/indicators/Tooltip/README.md +723 -0
- package/src/components/indicators/Tooltip/TooltipProvider.vue +142 -0
- package/src/components/indicators/Tooltip/theme.css +18 -0
- package/src/components/indicators/Tooltip/tooltip.ts +48 -0
- package/src/components/indicators/Tooltip/types.ts +15 -0
- package/src/components/indicators/Tooltip/useTooltip.ts +71 -0
- package/src/components/inputs/AutogrowTextView/AutogrowTextView.vue +110 -0
- package/src/components/inputs/AutogrowTextView/README.md +643 -0
- package/src/components/inputs/AutogrowTextView/theme.css +28 -0
- package/src/components/inputs/InputCard/InputCard.vue +600 -0
- package/src/components/inputs/InputCard/README.md +636 -0
- package/src/components/inputs/InputEmail/InputEmail.vue +698 -0
- package/src/components/inputs/InputEmail/README.md +764 -0
- package/src/components/inputs/InputNumber/InputNumber.vue +300 -0
- package/src/components/inputs/InputNumber/README.md +749 -0
- package/src/components/inputs/InputPhone/InputPhone.vue +645 -0
- package/src/components/inputs/InputPhone/README.md +636 -0
- package/src/components/inputs/InputSecure/InputSecure.vue +646 -0
- package/src/components/inputs/InputSecure/README.md +771 -0
- package/src/components/inputs/InputText/InputText.vue +225 -0
- package/src/components/inputs/InputText/README.md +844 -0
- package/src/components/inputs/OTP/OTP.vue +349 -0
- package/src/components/inputs/OTP/README.md +736 -0
- package/src/components/inputs/OTP/theme.css +50 -0
- package/src/components/inputs/StringCapture/README.md +718 -0
- package/src/components/inputs/StringCapture/StringCapture.vue +315 -0
- package/src/components/inputs/StringCapture/theme.css +86 -0
- package/src/components/inputs/Tags/README.md +897 -0
- package/src/components/inputs/Tags/TagBar.vue +793 -0
- package/src/components/inputs/Tags/TagCreation.vue +219 -0
- package/src/components/inputs/Tags/TagPicker.vue +380 -0
- package/src/components/inputs/Tags/tag-bar-styles.ts +354 -0
- package/src/components/inputs/Tags/theme.css +121 -0
- package/src/components/inputs/Tags/types.ts +346 -0
- package/src/components/inputs/search/README.md +759 -0
- package/src/components/inputs/search/SearchBar.vue +394 -0
- package/src/components/inputs/search/SearchResults.vue +310 -0
- package/src/components/inputs/search/theme.css +187 -0
- package/src/components/inputs/search/types.ts +8 -0
- package/src/components/inputs/theme.css +102 -0
- package/src/components/menus/ActionMenu/ActionMenu.vue +383 -0
- package/src/components/menus/ActionMenu/README.md +825 -0
- package/src/components/menus/ActionMenu/theme.css +93 -0
- package/src/components/models/Popover/Popover.vue +551 -0
- package/src/components/models/Popover/README.md +885 -0
- package/src/components/models/Popover/theme.css +52 -0
- package/src/components/models/Sheet/README.md +1159 -0
- package/src/components/models/Sheet/Sheet.vue +465 -0
- package/src/components/models/Sheet/theme.css +72 -0
- package/src/components/models/Sidebar/README.md +1228 -0
- package/src/components/models/Sidebar/Sidebar.vue +480 -0
- package/src/components/models/Sidebar/theme.css +90 -0
- package/src/components/navigation/adaptive/AdaptiveLayout.vue +779 -0
- package/src/components/navigation/adaptive/AdaptiveLayoutBreadcrumbs.vue +192 -0
- package/src/components/navigation/adaptive/AdaptiveLayoutMenuButton.vue +149 -0
- package/src/components/navigation/adaptive/README.md +768 -0
- package/src/components/navigation/adaptive/types.ts +19 -0
- package/src/components/navigation/adaptive/useAdaptiveLayout.ts +89 -0
- package/src/components/navigation/adaptive/useBreakpoints.ts +41 -0
- package/src/components/navigation/adaptive/useContainerMonitor.ts +214 -0
- package/src/components/navigation/adaptive/useViewAnimation.ts +721 -0
- package/src/components/navigation/adaptive/useViewResize.ts +211 -0
- package/src/components/navigation/navstack/NavigationStack.vue +180 -0
- package/src/components/navigation/navstack/README.md +994 -0
- package/src/components/navigation/navstack/useNavigationStack.ts +164 -0
- package/src/components/navigation/slideover/README.md +1275 -0
- package/src/components/navigation/slideover/SlideoverController.vue +287 -0
- package/src/components/navigation/slideover/useSlideoverController.ts +320 -0
- package/src/components/navigation/splitview/README.md +1115 -0
- package/src/components/navigation/splitview/SplitViewController.vue +176 -0
- package/src/components/navigation/splitview/useSplitViewController.ts +388 -0
- package/src/components/navigation/tabcontroller/README.md +919 -0
- package/src/components/navigation/tabcontroller/TabController.vue +307 -0
- package/src/components/navigation/tabcontroller/TabItem.vue +57 -0
- package/src/components/navigation/tabcontroller/types.ts +24 -0
- package/src/components/navigation/tabcontroller/useTabController.ts +18 -0
- package/src/components/navigation/theme.css +91 -0
- package/src/components/navigation/types.ts +7 -0
- package/src/components/pickers/CollectionPicker/CollectionPicker.vue +398 -0
- package/src/components/pickers/CollectionPicker/README.md +1115 -0
- package/src/components/pickers/CollectionPicker/theme.css +14 -0
- package/src/components/pickers/CollectionPicker/types.ts +11 -0
- package/src/components/pickers/ColorPicker/ColorPicker.vue +376 -0
- package/src/components/pickers/ColorPicker/README.md +1439 -0
- package/src/components/pickers/ColorPicker/colors.ts +299 -0
- package/src/components/pickers/ColorPicker/theme.css +32 -0
- package/src/components/pickers/DatePicker/DatePicker.vue +660 -0
- package/src/components/pickers/DatePicker/README.md +1195 -0
- package/src/components/pickers/DatePicker/theme.css +22 -0
- package/src/components/pickers/FilePicker/FilePicker.vue +534 -0
- package/src/components/pickers/FilePicker/README.md +1542 -0
- package/src/components/pickers/FilePicker/theme.css +48 -0
- package/src/components/pickers/FilePicker/types.ts +10 -0
- package/src/components/pickers/IconPicker/IconPicker.vue +327 -0
- package/src/components/pickers/IconPicker/README.md +1161 -0
- package/src/components/pickers/IconPicker/theme.css +28 -0
- package/src/components/pickers/theme.css +82 -0
- package/src/components/views/MarkdownViewer/MarkdownViewer.vue +442 -0
- package/src/components/views/MarkdownViewer/README.md +833 -0
- package/src/components/views/MarkdownViewer/theme.css +130 -0
- package/src/index.ts +263 -0
- package/src/theme.ts +378 -0
- package/src/themes/crimson-dark.ts +556 -0
- package/src/themes/cyan-light.ts +556 -0
- package/src/themes/dark.ts +557 -0
- package/src/themes/gold-dark.ts +556 -0
- package/src/themes/grass-dark.ts +556 -0
- package/src/themes/indigo.ts +556 -0
- package/src/themes/light.ts +557 -0
- package/src/themes/orange-dark.ts +557 -0
- package/src/themes/orange-light.ts +557 -0
- package/src/vue.d.ts +45 -0
|
@@ -0,0 +1,919 @@
|
|
|
1
|
+
# TabController
|
|
2
|
+
|
|
3
|
+
A flexible and feature-rich tab navigation component built with Vue 3 Composition API and TypeScript. The TabController provides an intuitive tabbed interface with support for icons, badges, keyboard navigation, and customizable layouts, perfect for organizing content into distinct sections.
|
|
4
|
+
|
|
5
|
+
## Installation/Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { TabController, TabItem, useTabController } from "@umbra-ui/core";
|
|
9
|
+
import type { Tab, TabPosition, TabControllerContext } from "@umbra-ui/core";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**Dependencies:**
|
|
13
|
+
|
|
14
|
+
- Vue 3.x
|
|
15
|
+
- @umbra-ui/icons (for icon support)
|
|
16
|
+
|
|
17
|
+
## Basic Usage
|
|
18
|
+
|
|
19
|
+
```vue
|
|
20
|
+
<script setup lang="ts">
|
|
21
|
+
import { ref } from "vue";
|
|
22
|
+
import { TabController, TabItem } from "@umbra-ui/core";
|
|
23
|
+
import HomeView from "./HomeView.vue";
|
|
24
|
+
import ProfileView from "./ProfileView.vue";
|
|
25
|
+
import SettingsView from "./SettingsView.vue";
|
|
26
|
+
|
|
27
|
+
const activeTab = ref("home");
|
|
28
|
+
|
|
29
|
+
const handleTabChange = (tabId: string) => {
|
|
30
|
+
console.log("Tab changed to:", tabId);
|
|
31
|
+
};
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div class="app">
|
|
36
|
+
<TabController
|
|
37
|
+
v-model="activeTab"
|
|
38
|
+
position="top"
|
|
39
|
+
layout="row"
|
|
40
|
+
@change="handleTabChange"
|
|
41
|
+
>
|
|
42
|
+
<TabItem id="home" label="Home" icon="home">
|
|
43
|
+
<HomeView />
|
|
44
|
+
</TabItem>
|
|
45
|
+
|
|
46
|
+
<TabItem id="profile" label="Profile" icon="user">
|
|
47
|
+
<ProfileView />
|
|
48
|
+
</TabItem>
|
|
49
|
+
|
|
50
|
+
<TabItem id="settings" label="Settings" icon="settings">
|
|
51
|
+
<SettingsView />
|
|
52
|
+
</TabItem>
|
|
53
|
+
</TabController>
|
|
54
|
+
</div>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<style module>
|
|
58
|
+
.app {
|
|
59
|
+
height: 100vh;
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Components
|
|
67
|
+
|
|
68
|
+
### TabController
|
|
69
|
+
|
|
70
|
+
The main container component that manages tab state and provides the tab bar interface.
|
|
71
|
+
|
|
72
|
+
#### Props
|
|
73
|
+
|
|
74
|
+
| Prop Name | Type | Required | Default | Description |
|
|
75
|
+
| -------------------- | ------------------- | -------- | ----------- | ------------------------------------------ |
|
|
76
|
+
| `modelValue` | `string` | No | `""` | The ID of the currently active tab |
|
|
77
|
+
| `position` | `TabPosition` | No | `"bottom"` | Position of the tab bar: "top" or "bottom" |
|
|
78
|
+
| `layout` | `"row" \| "column"` | No | `"column"` | Layout direction for tab items |
|
|
79
|
+
| `barColor` | `string` | No | `"#111111"` | Background color for the tab bar |
|
|
80
|
+
| `tabItemActiveColor` | `string` | No | `"#0090ff"` | Active tab item color |
|
|
81
|
+
|
|
82
|
+
#### Events
|
|
83
|
+
|
|
84
|
+
| Event Name | Payload Type | Description |
|
|
85
|
+
| ------------------- | ------------ | ----------------------------------- |
|
|
86
|
+
| `update:modelValue` | `string` | Emitted when the active tab changes |
|
|
87
|
+
| `change` | `string` | Emitted when a tab is selected |
|
|
88
|
+
|
|
89
|
+
#### Slots
|
|
90
|
+
|
|
91
|
+
| Slot Name | Description |
|
|
92
|
+
| --------- | ------------------ |
|
|
93
|
+
| `default` | TabItem components |
|
|
94
|
+
|
|
95
|
+
### TabItem
|
|
96
|
+
|
|
97
|
+
Individual tab component that represents a single tab and its content.
|
|
98
|
+
|
|
99
|
+
#### Props
|
|
100
|
+
|
|
101
|
+
| Prop Name | Type | Required | Default | Description |
|
|
102
|
+
| ---------- | ------------------ | -------- | ------- | ------------------------------- |
|
|
103
|
+
| `id` | `string` | Yes | - | Unique identifier for the tab |
|
|
104
|
+
| `label` | `string` | Yes | - | Display text for the tab |
|
|
105
|
+
| `icon` | `string` | No | - | Icon name from @umbra-ui/icons |
|
|
106
|
+
| `badge` | `string \| number` | No | - | Badge text or number to display |
|
|
107
|
+
| `disabled` | `boolean` | No | `false` | Whether the tab is disabled |
|
|
108
|
+
|
|
109
|
+
#### Slots
|
|
110
|
+
|
|
111
|
+
| Slot Name | Description |
|
|
112
|
+
| --------- | ----------------------------------------- |
|
|
113
|
+
| `default` | Content to display when the tab is active |
|
|
114
|
+
|
|
115
|
+
## Types
|
|
116
|
+
|
|
117
|
+
### Tab Interface
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
interface Tab {
|
|
121
|
+
id: string;
|
|
122
|
+
label: string;
|
|
123
|
+
icon?: string;
|
|
124
|
+
badge?: string | number;
|
|
125
|
+
disabled?: boolean;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### TabPosition Type
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
type TabPosition = "top" | "bottom";
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### TabControllerContext Interface
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
interface TabControllerContext {
|
|
139
|
+
tabs: Ref<Tab[]>;
|
|
140
|
+
activeTab: Ref<string>;
|
|
141
|
+
registerTab: (tab: Tab) => void;
|
|
142
|
+
unregisterTab: (id: string) => void;
|
|
143
|
+
setActiveTab: (id: string) => void;
|
|
144
|
+
goToTab: (index: number) => void;
|
|
145
|
+
nextTab: () => void;
|
|
146
|
+
previousTab: () => void;
|
|
147
|
+
animated: boolean;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## useTabController Composable
|
|
152
|
+
|
|
153
|
+
The `useTabController` composable provides access to the tab controller context and can be used within TabItem components or child components.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { useTabController } from "@umbra-ui/core";
|
|
157
|
+
|
|
158
|
+
const {
|
|
159
|
+
tabs,
|
|
160
|
+
activeTab,
|
|
161
|
+
registerTab,
|
|
162
|
+
unregisterTab,
|
|
163
|
+
setActiveTab,
|
|
164
|
+
goToTab,
|
|
165
|
+
nextTab,
|
|
166
|
+
previousTab,
|
|
167
|
+
animated,
|
|
168
|
+
} = useTabController();
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Composable Returns
|
|
172
|
+
|
|
173
|
+
| Property | Type | Description |
|
|
174
|
+
| --------------- | ------------------------- | ------------------------------------- |
|
|
175
|
+
| `tabs` | `Ref<Tab[]>` | Reactive array of all registered tabs |
|
|
176
|
+
| `activeTab` | `Ref<string>` | ID of the currently active tab |
|
|
177
|
+
| `registerTab` | `(tab: Tab) => void` | Register a new tab |
|
|
178
|
+
| `unregisterTab` | `(id: string) => void` | Unregister a tab |
|
|
179
|
+
| `setActiveTab` | `(id: string) => void` | Set the active tab |
|
|
180
|
+
| `goToTab` | `(index: number) => void` | Navigate to tab by index |
|
|
181
|
+
| `nextTab` | `() => void` | Navigate to the next tab |
|
|
182
|
+
| `previousTab` | `() => void` | Navigate to the previous tab |
|
|
183
|
+
| `animated` | `boolean` | Whether animations are enabled |
|
|
184
|
+
|
|
185
|
+
## CSS Customization
|
|
186
|
+
|
|
187
|
+
### Layout Variables
|
|
188
|
+
|
|
189
|
+
```css
|
|
190
|
+
.tab-controller {
|
|
191
|
+
--tabcontroller-container-bg: #ffffff;
|
|
192
|
+
--tabcontroller-bar-bg: #f8f9fa;
|
|
193
|
+
--tabcontroller-bar-border: #e9ecef;
|
|
194
|
+
--tabcontroller-tab-text: #495057;
|
|
195
|
+
--tabcontroller-tab-active-text: #007bff;
|
|
196
|
+
--tabcontroller-tab-hover-bg: #e9ecef;
|
|
197
|
+
--tabcontroller-tab-disabled-opacity: 0.5;
|
|
198
|
+
--tabcontroller-badge-bg: #dc3545;
|
|
199
|
+
--tabcontroller-badge-text: #ffffff;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Container Styling
|
|
204
|
+
|
|
205
|
+
```css
|
|
206
|
+
.tab-controller {
|
|
207
|
+
display: flex;
|
|
208
|
+
flex-direction: column;
|
|
209
|
+
height: 100%;
|
|
210
|
+
background: var(--tabcontroller-container-bg);
|
|
211
|
+
overflow: hidden;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Tab Bar Styling
|
|
216
|
+
|
|
217
|
+
```css
|
|
218
|
+
.tab-controller .tabBar {
|
|
219
|
+
display: flex;
|
|
220
|
+
background: var(--tabcontroller-bar-bg);
|
|
221
|
+
border-top: 1px solid var(--tabcontroller-bar-border);
|
|
222
|
+
padding: 0.353rem;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.tab-controller .tabBar--column {
|
|
226
|
+
gap: 0.353rem;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.tab-controller .tabBar--row {
|
|
230
|
+
gap: 0.5rem;
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Tab Item Styling
|
|
235
|
+
|
|
236
|
+
```css
|
|
237
|
+
.tab-controller .tabItem {
|
|
238
|
+
flex: 1;
|
|
239
|
+
display: flex;
|
|
240
|
+
align-items: center;
|
|
241
|
+
justify-content: center;
|
|
242
|
+
padding: 8px 4px;
|
|
243
|
+
background: none;
|
|
244
|
+
border: none;
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
transition: all 0.2s ease;
|
|
247
|
+
color: var(--tabcontroller-tab-text);
|
|
248
|
+
border-radius: 0.353rem;
|
|
249
|
+
font-size: 12px;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.tab-controller .tabItem--active {
|
|
253
|
+
color: var(--tabcontroller-tab-active-text);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.tab-controller .tabItem--disabled {
|
|
257
|
+
opacity: var(--tabcontroller-tab-disabled-opacity);
|
|
258
|
+
cursor: not-allowed;
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Examples
|
|
263
|
+
|
|
264
|
+
### Mobile App Navigation
|
|
265
|
+
|
|
266
|
+
```vue
|
|
267
|
+
<script setup lang="ts">
|
|
268
|
+
import { ref } from "vue";
|
|
269
|
+
import { TabController, TabItem } from "@umbra-ui/core";
|
|
270
|
+
import HomeScreen from "./HomeScreen.vue";
|
|
271
|
+
import SearchScreen from "./SearchScreen.vue";
|
|
272
|
+
import ProfileScreen from "./ProfileScreen.vue";
|
|
273
|
+
import NotificationsScreen from "./NotificationsScreen.vue";
|
|
274
|
+
|
|
275
|
+
const activeTab = ref("home");
|
|
276
|
+
|
|
277
|
+
const handleTabChange = (tabId: string) => {
|
|
278
|
+
console.log("Navigated to:", tabId);
|
|
279
|
+
};
|
|
280
|
+
</script>
|
|
281
|
+
|
|
282
|
+
<template>
|
|
283
|
+
<div class="mobile-app">
|
|
284
|
+
<TabController
|
|
285
|
+
v-model="activeTab"
|
|
286
|
+
position="bottom"
|
|
287
|
+
layout="column"
|
|
288
|
+
@change="handleTabChange"
|
|
289
|
+
>
|
|
290
|
+
<TabItem id="home" label="Home" icon="home">
|
|
291
|
+
<HomeScreen />
|
|
292
|
+
</TabItem>
|
|
293
|
+
|
|
294
|
+
<TabItem id="search" label="Search" icon="search">
|
|
295
|
+
<SearchScreen />
|
|
296
|
+
</TabItem>
|
|
297
|
+
|
|
298
|
+
<TabItem id="notifications" label="Notifications" icon="bell" :badge="5">
|
|
299
|
+
<NotificationsScreen />
|
|
300
|
+
</TabItem>
|
|
301
|
+
|
|
302
|
+
<TabItem id="profile" label="Profile" icon="user">
|
|
303
|
+
<ProfileScreen />
|
|
304
|
+
</TabItem>
|
|
305
|
+
</TabController>
|
|
306
|
+
</div>
|
|
307
|
+
</template>
|
|
308
|
+
|
|
309
|
+
<style module>
|
|
310
|
+
.mobile-app {
|
|
311
|
+
height: 100vh;
|
|
312
|
+
display: flex;
|
|
313
|
+
flex-direction: column;
|
|
314
|
+
background: #f8f9fa;
|
|
315
|
+
}
|
|
316
|
+
</style>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Desktop Application Tabs
|
|
320
|
+
|
|
321
|
+
```vue
|
|
322
|
+
<script setup lang="ts">
|
|
323
|
+
import { ref } from "vue";
|
|
324
|
+
import { TabController, TabItem } from "@umbra-ui/core";
|
|
325
|
+
import DashboardView from "./DashboardView.vue";
|
|
326
|
+
import AnalyticsView from "./AnalyticsView.vue";
|
|
327
|
+
import ReportsView from "./ReportsView.vue";
|
|
328
|
+
import SettingsView from "./SettingsView.vue";
|
|
329
|
+
|
|
330
|
+
const activeTab = ref("dashboard");
|
|
331
|
+
|
|
332
|
+
const handleTabChange = (tabId: string) => {
|
|
333
|
+
console.log("Tab changed to:", tabId);
|
|
334
|
+
};
|
|
335
|
+
</script>
|
|
336
|
+
|
|
337
|
+
<template>
|
|
338
|
+
<div class="desktop-app">
|
|
339
|
+
<header class="app-header">
|
|
340
|
+
<h1 class="app-title">My Application</h1>
|
|
341
|
+
</header>
|
|
342
|
+
|
|
343
|
+
<TabController
|
|
344
|
+
v-model="activeTab"
|
|
345
|
+
position="top"
|
|
346
|
+
layout="row"
|
|
347
|
+
@change="handleTabChange"
|
|
348
|
+
>
|
|
349
|
+
<TabItem id="dashboard" label="Dashboard" icon="dashboard">
|
|
350
|
+
<DashboardView />
|
|
351
|
+
</TabItem>
|
|
352
|
+
|
|
353
|
+
<TabItem id="analytics" label="Analytics" icon="chart">
|
|
354
|
+
<AnalyticsView />
|
|
355
|
+
</TabItem>
|
|
356
|
+
|
|
357
|
+
<TabItem id="reports" label="Reports" icon="file">
|
|
358
|
+
<ReportsView />
|
|
359
|
+
</TabItem>
|
|
360
|
+
|
|
361
|
+
<TabItem id="settings" label="Settings" icon="settings">
|
|
362
|
+
<SettingsView />
|
|
363
|
+
</TabItem>
|
|
364
|
+
</TabController>
|
|
365
|
+
</div>
|
|
366
|
+
</template>
|
|
367
|
+
|
|
368
|
+
<style module>
|
|
369
|
+
.desktop-app {
|
|
370
|
+
height: 100vh;
|
|
371
|
+
display: flex;
|
|
372
|
+
flex-direction: column;
|
|
373
|
+
background: #ffffff;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.app-header {
|
|
377
|
+
padding: 16px 24px;
|
|
378
|
+
background: #f8f9fa;
|
|
379
|
+
border-bottom: 1px solid #e9ecef;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.app-title {
|
|
383
|
+
font-size: 24px;
|
|
384
|
+
font-weight: 600;
|
|
385
|
+
color: #212529;
|
|
386
|
+
margin: 0;
|
|
387
|
+
}
|
|
388
|
+
</style>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Settings Panel with Disabled Tabs
|
|
392
|
+
|
|
393
|
+
```vue
|
|
394
|
+
<script setup lang="ts">
|
|
395
|
+
import { ref } from "vue";
|
|
396
|
+
import { TabController, TabItem } from "@umbra-ui/core";
|
|
397
|
+
import GeneralSettings from "./GeneralSettings.vue";
|
|
398
|
+
import AppearanceSettings from "./AppearanceSettings.vue";
|
|
399
|
+
import PrivacySettings from "./PrivacySettings.vue";
|
|
400
|
+
import AdvancedSettings from "./AdvancedSettings.vue";
|
|
401
|
+
|
|
402
|
+
const activeTab = ref("general");
|
|
403
|
+
const isProUser = ref(false);
|
|
404
|
+
|
|
405
|
+
const handleTabChange = (tabId: string) => {
|
|
406
|
+
console.log("Settings tab changed to:", tabId);
|
|
407
|
+
};
|
|
408
|
+
</script>
|
|
409
|
+
|
|
410
|
+
<template>
|
|
411
|
+
<div class="settings-panel">
|
|
412
|
+
<div class="settings-header">
|
|
413
|
+
<h2 class="settings-title">Settings</h2>
|
|
414
|
+
<p class="settings-subtitle">Configure your application preferences</p>
|
|
415
|
+
</div>
|
|
416
|
+
|
|
417
|
+
<TabController
|
|
418
|
+
v-model="activeTab"
|
|
419
|
+
position="top"
|
|
420
|
+
layout="row"
|
|
421
|
+
@change="handleTabChange"
|
|
422
|
+
>
|
|
423
|
+
<TabItem id="general" label="General" icon="settings">
|
|
424
|
+
<GeneralSettings />
|
|
425
|
+
</TabItem>
|
|
426
|
+
|
|
427
|
+
<TabItem id="appearance" label="Appearance" icon="palette">
|
|
428
|
+
<AppearanceSettings />
|
|
429
|
+
</TabItem>
|
|
430
|
+
|
|
431
|
+
<TabItem
|
|
432
|
+
id="privacy"
|
|
433
|
+
label="Privacy"
|
|
434
|
+
icon="shield"
|
|
435
|
+
:disabled="!isProUser"
|
|
436
|
+
:badge="!isProUser ? 'Pro' : undefined"
|
|
437
|
+
>
|
|
438
|
+
<PrivacySettings />
|
|
439
|
+
</TabItem>
|
|
440
|
+
|
|
441
|
+
<TabItem
|
|
442
|
+
id="advanced"
|
|
443
|
+
label="Advanced"
|
|
444
|
+
icon="gear"
|
|
445
|
+
:disabled="!isProUser"
|
|
446
|
+
:badge="!isProUser ? 'Pro' : undefined"
|
|
447
|
+
>
|
|
448
|
+
<AdvancedSettings />
|
|
449
|
+
</TabItem>
|
|
450
|
+
</TabController>
|
|
451
|
+
|
|
452
|
+
<div class="settings-footer">
|
|
453
|
+
<button v-if="!isProUser" @click="isProUser = true" class="upgrade-btn">
|
|
454
|
+
Upgrade to Pro
|
|
455
|
+
</button>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
</template>
|
|
459
|
+
|
|
460
|
+
<style module>
|
|
461
|
+
.settings-panel {
|
|
462
|
+
height: 100vh;
|
|
463
|
+
display: flex;
|
|
464
|
+
flex-direction: column;
|
|
465
|
+
background: #f8f9fa;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.settings-header {
|
|
469
|
+
padding: 24px;
|
|
470
|
+
background: white;
|
|
471
|
+
border-bottom: 1px solid #e9ecef;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.settings-title {
|
|
475
|
+
font-size: 28px;
|
|
476
|
+
font-weight: 700;
|
|
477
|
+
color: #1a202c;
|
|
478
|
+
margin: 0 0 8px 0;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.settings-subtitle {
|
|
482
|
+
font-size: 16px;
|
|
483
|
+
color: #718096;
|
|
484
|
+
margin: 0;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.settings-footer {
|
|
488
|
+
padding: 16px 24px;
|
|
489
|
+
background: white;
|
|
490
|
+
border-top: 1px solid #e9ecef;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.upgrade-btn {
|
|
494
|
+
padding: 12px 24px;
|
|
495
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
496
|
+
color: white;
|
|
497
|
+
border: none;
|
|
498
|
+
border-radius: 8px;
|
|
499
|
+
font-weight: 600;
|
|
500
|
+
cursor: pointer;
|
|
501
|
+
transition: all 0.2s ease;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.upgrade-btn:hover {
|
|
505
|
+
transform: translateY(-1px);
|
|
506
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
507
|
+
}
|
|
508
|
+
</style>
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Dynamic Tab Management
|
|
512
|
+
|
|
513
|
+
```vue
|
|
514
|
+
<script setup lang="ts">
|
|
515
|
+
import { ref } from "vue";
|
|
516
|
+
import { TabController, TabItem, useTabController } from "@umbra-ui/core";
|
|
517
|
+
import DynamicView from "./DynamicView.vue";
|
|
518
|
+
|
|
519
|
+
const activeTab = ref("tab1");
|
|
520
|
+
const tabCounter = ref(2);
|
|
521
|
+
|
|
522
|
+
const tabs = ref([
|
|
523
|
+
{ id: "tab1", label: "Tab 1", icon: "home" },
|
|
524
|
+
{ id: "tab2", label: "Tab 2", icon: "user" },
|
|
525
|
+
]);
|
|
526
|
+
|
|
527
|
+
const addTab = () => {
|
|
528
|
+
const newTab = {
|
|
529
|
+
id: `tab${tabCounter.value}`,
|
|
530
|
+
label: `Tab ${tabCounter.value}`,
|
|
531
|
+
icon: "plus",
|
|
532
|
+
};
|
|
533
|
+
tabs.value.push(newTab);
|
|
534
|
+
activeTab.value = newTab.id;
|
|
535
|
+
tabCounter.value++;
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
const removeTab = (tabId: string) => {
|
|
539
|
+
if (tabs.value.length > 1) {
|
|
540
|
+
const index = tabs.value.findIndex((tab) => tab.id === tabId);
|
|
541
|
+
if (index > -1) {
|
|
542
|
+
tabs.value.splice(index, 1);
|
|
543
|
+
if (activeTab.value === tabId) {
|
|
544
|
+
activeTab.value = tabs.value[Math.max(0, index - 1)].id;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
const handleTabChange = (tabId: string) => {
|
|
551
|
+
console.log("Tab changed to:", tabId);
|
|
552
|
+
};
|
|
553
|
+
</script>
|
|
554
|
+
|
|
555
|
+
<template>
|
|
556
|
+
<div class="dynamic-tabs">
|
|
557
|
+
<div class="tab-controls">
|
|
558
|
+
<button @click="addTab" class="control-btn add">Add Tab</button>
|
|
559
|
+
<button
|
|
560
|
+
v-for="tab in tabs"
|
|
561
|
+
:key="tab.id"
|
|
562
|
+
@click="removeTab(tab.id)"
|
|
563
|
+
:disabled="tabs.length <= 1"
|
|
564
|
+
class="control-btn remove"
|
|
565
|
+
>
|
|
566
|
+
Remove {{ tab.label }}
|
|
567
|
+
</button>
|
|
568
|
+
</div>
|
|
569
|
+
|
|
570
|
+
<TabController
|
|
571
|
+
v-model="activeTab"
|
|
572
|
+
position="top"
|
|
573
|
+
layout="row"
|
|
574
|
+
@change="handleTabChange"
|
|
575
|
+
>
|
|
576
|
+
<TabItem
|
|
577
|
+
v-for="tab in tabs"
|
|
578
|
+
:key="tab.id"
|
|
579
|
+
:id="tab.id"
|
|
580
|
+
:label="tab.label"
|
|
581
|
+
:icon="tab.icon"
|
|
582
|
+
>
|
|
583
|
+
<DynamicView :tab-id="tab.id" />
|
|
584
|
+
</TabItem>
|
|
585
|
+
</TabController>
|
|
586
|
+
</div>
|
|
587
|
+
</template>
|
|
588
|
+
|
|
589
|
+
<style module>
|
|
590
|
+
.dynamic-tabs {
|
|
591
|
+
height: 100vh;
|
|
592
|
+
display: flex;
|
|
593
|
+
flex-direction: column;
|
|
594
|
+
background: #f8f9fa;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.tab-controls {
|
|
598
|
+
display: flex;
|
|
599
|
+
gap: 8px;
|
|
600
|
+
padding: 16px;
|
|
601
|
+
background: white;
|
|
602
|
+
border-bottom: 1px solid #e9ecef;
|
|
603
|
+
flex-wrap: wrap;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.control-btn {
|
|
607
|
+
padding: 8px 16px;
|
|
608
|
+
border: 1px solid #dee2e6;
|
|
609
|
+
border-radius: 6px;
|
|
610
|
+
cursor: pointer;
|
|
611
|
+
font-size: 14px;
|
|
612
|
+
transition: all 0.2s ease;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.control-btn.add {
|
|
616
|
+
background: #28a745;
|
|
617
|
+
color: white;
|
|
618
|
+
border-color: #28a745;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.control-btn.add:hover {
|
|
622
|
+
background: #218838;
|
|
623
|
+
border-color: #1e7e34;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.control-btn.remove {
|
|
627
|
+
background: #dc3545;
|
|
628
|
+
color: white;
|
|
629
|
+
border-color: #dc3545;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.control-btn.remove:hover:not(:disabled) {
|
|
633
|
+
background: #c82333;
|
|
634
|
+
border-color: #bd2130;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.control-btn:disabled {
|
|
638
|
+
opacity: 0.5;
|
|
639
|
+
cursor: not-allowed;
|
|
640
|
+
}
|
|
641
|
+
</style>
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### Custom Tab Content with Navigation
|
|
645
|
+
|
|
646
|
+
```vue
|
|
647
|
+
<script setup lang="ts">
|
|
648
|
+
import { ref } from "vue";
|
|
649
|
+
import { TabController, TabItem, useTabController } from "@umbra-ui/core";
|
|
650
|
+
import CustomView from "./CustomView.vue";
|
|
651
|
+
|
|
652
|
+
const activeTab = ref("view1");
|
|
653
|
+
|
|
654
|
+
const handleTabChange = (tabId: string) => {
|
|
655
|
+
console.log("Tab changed to:", tabId);
|
|
656
|
+
};
|
|
657
|
+
</script>
|
|
658
|
+
|
|
659
|
+
<template>
|
|
660
|
+
<div class="custom-tabs">
|
|
661
|
+
<TabController
|
|
662
|
+
v-model="activeTab"
|
|
663
|
+
position="top"
|
|
664
|
+
layout="row"
|
|
665
|
+
@change="handleTabChange"
|
|
666
|
+
>
|
|
667
|
+
<TabItem id="view1" label="View 1" icon="eye">
|
|
668
|
+
<CustomView
|
|
669
|
+
title="First View"
|
|
670
|
+
:can-navigate="true"
|
|
671
|
+
@navigate="activeTab = 'view2'"
|
|
672
|
+
/>
|
|
673
|
+
</TabItem>
|
|
674
|
+
|
|
675
|
+
<TabItem id="view2" label="View 2" icon="eye">
|
|
676
|
+
<CustomView
|
|
677
|
+
title="Second View"
|
|
678
|
+
:can-navigate="true"
|
|
679
|
+
@navigate="activeTab = 'view3'"
|
|
680
|
+
/>
|
|
681
|
+
</TabItem>
|
|
682
|
+
|
|
683
|
+
<TabItem id="view3" label="View 3" icon="eye">
|
|
684
|
+
<CustomView title="Third View" :can-navigate="false" />
|
|
685
|
+
</TabItem>
|
|
686
|
+
</TabController>
|
|
687
|
+
</div>
|
|
688
|
+
</template>
|
|
689
|
+
|
|
690
|
+
<style module>
|
|
691
|
+
.custom-tabs {
|
|
692
|
+
height: 100vh;
|
|
693
|
+
display: flex;
|
|
694
|
+
flex-direction: column;
|
|
695
|
+
background: #ffffff;
|
|
696
|
+
}
|
|
697
|
+
</style>
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
## Advanced Usage
|
|
701
|
+
|
|
702
|
+
### Programmatic Tab Control
|
|
703
|
+
|
|
704
|
+
```vue
|
|
705
|
+
<script setup lang="ts">
|
|
706
|
+
import { ref } from "vue";
|
|
707
|
+
import { TabController, TabItem, useTabController } from "@umbra-ui/core";
|
|
708
|
+
|
|
709
|
+
const activeTab = ref("tab1");
|
|
710
|
+
const tabControllerRef = ref();
|
|
711
|
+
|
|
712
|
+
// Access tab controller methods
|
|
713
|
+
const nextTab = () => {
|
|
714
|
+
// This would need to be implemented in the TabController component
|
|
715
|
+
console.log("Navigate to next tab");
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
const previousTab = () => {
|
|
719
|
+
console.log("Navigate to previous tab");
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
const goToTab = (index: number) => {
|
|
723
|
+
console.log("Go to tab index:", index);
|
|
724
|
+
};
|
|
725
|
+
</script>
|
|
726
|
+
|
|
727
|
+
<template>
|
|
728
|
+
<div class="programmatic-tabs">
|
|
729
|
+
<div class="tab-navigation">
|
|
730
|
+
<button @click="nextTab" class="nav-btn">Next Tab</button>
|
|
731
|
+
<button @click="previousTab" class="nav-btn">Previous Tab</button>
|
|
732
|
+
<button @click="goToTab(0)" class="nav-btn">Go to Tab 1</button>
|
|
733
|
+
<button @click="goToTab(1)" class="nav-btn">Go to Tab 2</button>
|
|
734
|
+
</div>
|
|
735
|
+
|
|
736
|
+
<TabController
|
|
737
|
+
ref="tabControllerRef"
|
|
738
|
+
v-model="activeTab"
|
|
739
|
+
position="top"
|
|
740
|
+
layout="row"
|
|
741
|
+
>
|
|
742
|
+
<TabItem id="tab1" label="Tab 1" icon="home">
|
|
743
|
+
<div class="tab-content">Content for Tab 1</div>
|
|
744
|
+
</TabItem>
|
|
745
|
+
|
|
746
|
+
<TabItem id="tab2" label="Tab 2" icon="user">
|
|
747
|
+
<div class="tab-content">Content for Tab 2</div>
|
|
748
|
+
</TabItem>
|
|
749
|
+
</TabController>
|
|
750
|
+
</div>
|
|
751
|
+
</template>
|
|
752
|
+
|
|
753
|
+
<style module>
|
|
754
|
+
.programmatic-tabs {
|
|
755
|
+
height: 100vh;
|
|
756
|
+
display: flex;
|
|
757
|
+
flex-direction: column;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.tab-navigation {
|
|
761
|
+
display: flex;
|
|
762
|
+
gap: 8px;
|
|
763
|
+
padding: 16px;
|
|
764
|
+
background: #f8f9fa;
|
|
765
|
+
border-bottom: 1px solid #e9ecef;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
.nav-btn {
|
|
769
|
+
padding: 8px 16px;
|
|
770
|
+
background: #007bff;
|
|
771
|
+
color: white;
|
|
772
|
+
border: none;
|
|
773
|
+
border-radius: 6px;
|
|
774
|
+
cursor: pointer;
|
|
775
|
+
font-size: 14px;
|
|
776
|
+
transition: all 0.2s ease;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
.nav-btn:hover {
|
|
780
|
+
background: #0056b3;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.tab-content {
|
|
784
|
+
padding: 24px;
|
|
785
|
+
font-size: 16px;
|
|
786
|
+
color: #495057;
|
|
787
|
+
}
|
|
788
|
+
</style>
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Integration with Router
|
|
792
|
+
|
|
793
|
+
```vue
|
|
794
|
+
<script setup lang="ts">
|
|
795
|
+
import { ref, watch } from "vue";
|
|
796
|
+
import { useRoute, useRouter } from "vue-router";
|
|
797
|
+
import { TabController, TabItem } from "@umbra-ui/core";
|
|
798
|
+
|
|
799
|
+
const route = useRoute();
|
|
800
|
+
const router = useRouter();
|
|
801
|
+
const activeTab = ref("home");
|
|
802
|
+
|
|
803
|
+
const tabs = [
|
|
804
|
+
{ id: "home", label: "Home", icon: "home", route: "/" },
|
|
805
|
+
{ id: "about", label: "About", icon: "info", route: "/about" },
|
|
806
|
+
{ id: "contact", label: "Contact", icon: "mail", route: "/contact" },
|
|
807
|
+
];
|
|
808
|
+
|
|
809
|
+
// Sync with router
|
|
810
|
+
watch(
|
|
811
|
+
() => route.path,
|
|
812
|
+
(newPath) => {
|
|
813
|
+
const tab = tabs.find((t) => t.route === newPath);
|
|
814
|
+
if (tab) {
|
|
815
|
+
activeTab.value = tab.id;
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
{ immediate: true }
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
const handleTabChange = (tabId: string) => {
|
|
822
|
+
const tab = tabs.find((t) => t.id === tabId);
|
|
823
|
+
if (tab) {
|
|
824
|
+
router.push(tab.route);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
</script>
|
|
828
|
+
|
|
829
|
+
<template>
|
|
830
|
+
<div class="routed-tabs">
|
|
831
|
+
<TabController
|
|
832
|
+
v-model="activeTab"
|
|
833
|
+
position="top"
|
|
834
|
+
layout="row"
|
|
835
|
+
@change="handleTabChange"
|
|
836
|
+
>
|
|
837
|
+
<TabItem
|
|
838
|
+
v-for="tab in tabs"
|
|
839
|
+
:key="tab.id"
|
|
840
|
+
:id="tab.id"
|
|
841
|
+
:label="tab.label"
|
|
842
|
+
:icon="tab.icon"
|
|
843
|
+
>
|
|
844
|
+
<router-view />
|
|
845
|
+
</TabItem>
|
|
846
|
+
</TabController>
|
|
847
|
+
</div>
|
|
848
|
+
</template>
|
|
849
|
+
|
|
850
|
+
<style module>
|
|
851
|
+
.routed-tabs {
|
|
852
|
+
height: 100vh;
|
|
853
|
+
display: flex;
|
|
854
|
+
flex-direction: column;
|
|
855
|
+
}
|
|
856
|
+
</style>
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
## Performance Considerations
|
|
860
|
+
|
|
861
|
+
- **Component Registration**: Tabs are automatically registered and unregistered when mounted/unmounted
|
|
862
|
+
- **Reactive Updates**: Tab properties are watched and updated reactively
|
|
863
|
+
- **Memory Management**: Proper cleanup when tabs are removed
|
|
864
|
+
- **Icon Loading**: Icons are loaded on-demand from the @umbra-ui/icons package
|
|
865
|
+
|
|
866
|
+
## Accessibility
|
|
867
|
+
|
|
868
|
+
- **Keyboard Navigation**: Support for arrow keys and tab navigation
|
|
869
|
+
- **Screen Reader Support**: Proper ARIA labels and roles
|
|
870
|
+
- **Focus Management**: Focus is maintained during tab changes
|
|
871
|
+
- **Disabled State**: Proper handling of disabled tabs
|
|
872
|
+
|
|
873
|
+
## Browser Support
|
|
874
|
+
|
|
875
|
+
- **Modern Browsers**: Chrome 88+, Firefox 85+, Safari 14+, Edge 88+
|
|
876
|
+
- **Mobile Browsers**: iOS Safari 14+, Chrome Mobile 88+
|
|
877
|
+
- **CSS Features**: Uses CSS Grid and modern layout features
|
|
878
|
+
- **JavaScript**: Requires ES2020+ support
|
|
879
|
+
|
|
880
|
+
## Troubleshooting
|
|
881
|
+
|
|
882
|
+
### Common Issues
|
|
883
|
+
|
|
884
|
+
1. **Tabs not displaying**: Ensure TabItem components are properly nested within TabController
|
|
885
|
+
2. **Icons not showing**: Verify icon names match those available in @umbra-ui/icons
|
|
886
|
+
3. **Active tab not updating**: Check that the modelValue prop is properly bound
|
|
887
|
+
4. **Content not showing**: Ensure TabItem components have content in their default slot
|
|
888
|
+
|
|
889
|
+
### Debug Mode
|
|
890
|
+
|
|
891
|
+
```vue
|
|
892
|
+
<script setup lang="ts">
|
|
893
|
+
import { ref, watch } from "vue";
|
|
894
|
+
import { TabController, TabItem, useTabController } from "@umbra-ui/core";
|
|
895
|
+
|
|
896
|
+
const activeTab = ref("tab1");
|
|
897
|
+
|
|
898
|
+
// Watch for tab changes
|
|
899
|
+
watch(activeTab, (newTab) => {
|
|
900
|
+
console.log("Active tab changed to:", newTab);
|
|
901
|
+
});
|
|
902
|
+
</script>
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
## Migration Guide
|
|
906
|
+
|
|
907
|
+
### From v1 to v2
|
|
908
|
+
|
|
909
|
+
- `v-model` prop is now `modelValue`
|
|
910
|
+
- `position` prop now supports "top" and "bottom" values
|
|
911
|
+
- `layout` prop now supports "row" and "column" values
|
|
912
|
+
- Icon system has been updated to use @umbra-ui/icons
|
|
913
|
+
|
|
914
|
+
### Breaking Changes
|
|
915
|
+
|
|
916
|
+
- Removed `activeTab` prop in favor of `modelValue`
|
|
917
|
+
- Changed event names from `tab-change` to `change`
|
|
918
|
+
- Updated CSS class naming convention
|
|
919
|
+
- Modified icon prop to use string names instead of components
|