@pgcorp/ui-kit 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/LICENSE +21 -0
- package/README.md +157 -0
- package/docs/accessibility.md +27 -0
- package/docs/getting-started.md +80 -0
- package/docs/licensing.md +30 -0
- package/docs/public-api.md +39 -0
- package/docs/security.md +21 -0
- package/docs/theming.md +35 -0
- package/docs/versioning.md +23 -0
- package/index.ts +3 -0
- package/package.json +592 -0
- package/src/components/icons/_internal/generatedSputnigUiIconMarkup.ts +971 -0
- package/src/components/icons/sputnigUiIcons.ts +530 -0
- package/src/components/layout/SAppShell.vue +232 -0
- package/src/components/layout/SDockRegion.vue +200 -0
- package/src/components/layout/SResizeHandle.vue +251 -0
- package/src/components/layout/SSideMenuButton.vue +104 -0
- package/src/components/layout/SSideMenuRail.vue +47 -0
- package/src/components/layout/SStack.css +25 -0
- package/src/components/layout/SStack.vue +57 -0
- package/src/components/layout/SStatusBarShell.css +15 -0
- package/src/components/layout/SStatusBarShell.vue +26 -0
- package/src/components/layout/SWorkbenchLayout.vue +53 -0
- package/src/components/layout/SWorkbenchPanelShell.css +161 -0
- package/src/components/layout/SWorkbenchPanelShell.vue +286 -0
- package/src/components/layout/_internal/SAppBrand.css +45 -0
- package/src/components/layout/_internal/SAppBrand.vue +37 -0
- package/src/components/layout/_internal/SAppBrandContent.vue +30 -0
- package/src/components/layout/types.ts +28 -0
- package/src/components/shared/_internal/SDialogSurface.css +127 -0
- package/src/components/shared/_internal/SDialogSurface.vue +117 -0
- package/src/components/shared/_internal/SDisclosureSummary.css +92 -0
- package/src/components/shared/_internal/SDisclosureSummary.vue +121 -0
- package/src/components/shared/_internal/SListboxGroup.css +18 -0
- package/src/components/shared/_internal/SListboxGroup.vue +24 -0
- package/src/components/shared/_internal/SMenuItem.css +112 -0
- package/src/components/shared/_internal/SMenuItem.vue +213 -0
- package/src/components/shared/_internal/SMenuLayer.css +21 -0
- package/src/components/shared/_internal/SMenuLayer.vue +48 -0
- package/src/components/shared/_internal/SMenuSeparator.css +5 -0
- package/src/components/shared/_internal/SMenuSeparator.vue +13 -0
- package/src/components/shared/_internal/SToastItem.css +65 -0
- package/src/components/shared/_internal/SToastItem.vue +58 -0
- package/src/components/shared/_internal/STreeNode.vue +761 -0
- package/src/components/shared/_internal/dialogSurface.ts +105 -0
- package/src/components/shared/_internal/focusTrap.ts +40 -0
- package/src/components/shared/_internal/listboxContext.ts +16 -0
- package/src/components/shared/_internal/markerContract.ts +10 -0
- package/src/components/shared/_internal/menuContext.ts +23 -0
- package/src/components/shared/_internal/selectionContract.ts +239 -0
- package/src/components/shared/_internal/sidebarGroupContext.ts +31 -0
- package/src/components/shared/_internal/tabsContext.ts +57 -0
- package/src/components/shared/_internal/treeRuntime.ts +203 -0
- package/src/components/shared/_internal/useSelectionRoving.ts +162 -0
- package/src/components/shared/codeLanguages.ts +168 -0
- package/src/components/shared/complex/SKanbanCard.css +8 -0
- package/src/components/shared/complex/SKanbanCard.vue +76 -0
- package/src/components/shared/complex/SKanbanLane.vue +82 -0
- package/src/components/shared/complex/STree.css +24 -0
- package/src/components/shared/complex/STree.vue +288 -0
- package/src/components/shared/complex/treeAdapter.ts +210 -0
- package/src/components/shared/complex/types.ts +102 -0
- package/src/components/shared/containers/SCloudChrome.css +103 -0
- package/src/components/shared/containers/SCloudChrome.vue +87 -0
- package/src/components/shared/containers/SCloudChromeEmpty.css +10 -0
- package/src/components/shared/containers/SCloudChromeEmpty.vue +18 -0
- package/src/components/shared/containers/SCloudChromeRow.css +78 -0
- package/src/components/shared/containers/SCloudChromeRow.vue +108 -0
- package/src/components/shared/containers/SCollapsiblePanel.css +57 -0
- package/src/components/shared/containers/SCollapsiblePanel.vue +360 -0
- package/src/components/shared/containers/SDrawer.css +60 -0
- package/src/components/shared/containers/SDrawer.vue +86 -0
- package/src/components/shared/containers/SFormGrid.css +26 -0
- package/src/components/shared/containers/SFormGrid.vue +46 -0
- package/src/components/shared/containers/SFormGridItem.css +18 -0
- package/src/components/shared/containers/SFormGridItem.vue +29 -0
- package/src/components/shared/containers/SLeftSidebar.vue +74 -0
- package/src/components/shared/containers/SModal.css +27 -0
- package/src/components/shared/containers/SModal.vue +105 -0
- package/src/components/shared/containers/SPageHeader.css +66 -0
- package/src/components/shared/containers/SPageHeader.vue +45 -0
- package/src/components/shared/containers/SPanel.css +134 -0
- package/src/components/shared/containers/SPanel.vue +169 -0
- package/src/components/shared/containers/SPopover.css +35 -0
- package/src/components/shared/containers/SPopover.vue +551 -0
- package/src/components/shared/containers/SSidebarGroup.css +12 -0
- package/src/components/shared/containers/SSidebarGroup.vue +443 -0
- package/src/components/shared/containers/SSidebarSection.css +41 -0
- package/src/components/shared/containers/SSidebarSection.vue +268 -0
- package/src/components/shared/containers/disclosureSummary.ts +48 -0
- package/src/components/shared/containers/popover.ts +72 -0
- package/src/components/shared/containers/sidebar.ts +206 -0
- package/src/components/shared/controls/SButton.css +274 -0
- package/src/components/shared/controls/SButton.vue +211 -0
- package/src/components/shared/controls/SCheckbox.css +65 -0
- package/src/components/shared/controls/SCheckbox.vue +130 -0
- package/src/components/shared/controls/SCheckboxGroup.vue +134 -0
- package/src/components/shared/controls/SChoiceCards.css +29 -0
- package/src/components/shared/controls/SChoiceCards.vue +145 -0
- package/src/components/shared/controls/SColorSelect.vue +146 -0
- package/src/components/shared/controls/SComboboxTrigger.css +35 -0
- package/src/components/shared/controls/SComboboxTrigger.vue +122 -0
- package/src/components/shared/controls/SContextMenu.css +11 -0
- package/src/components/shared/controls/SContextMenu.vue +186 -0
- package/src/components/shared/controls/SContextToggleButton.vue +64 -0
- package/src/components/shared/controls/SDragHandle.vue +100 -0
- package/src/components/shared/controls/SDropdownMenu.css +5 -0
- package/src/components/shared/controls/SDropdownMenu.vue +188 -0
- package/src/components/shared/controls/SField.css +79 -0
- package/src/components/shared/controls/SField.vue +89 -0
- package/src/components/shared/controls/SFieldGroup.css +58 -0
- package/src/components/shared/controls/SFieldGroup.vue +117 -0
- package/src/components/shared/controls/SFieldLabel.css +12 -0
- package/src/components/shared/controls/SFieldLabel.vue +51 -0
- package/src/components/shared/controls/SInlineTokenEditor.css +61 -0
- package/src/components/shared/controls/SInlineTokenEditor.vue +1081 -0
- package/src/components/shared/controls/SInputText.css +66 -0
- package/src/components/shared/controls/SInputText.vue +321 -0
- package/src/components/shared/controls/SInteractiveSurface.css +72 -0
- package/src/components/shared/controls/SInteractiveSurface.vue +326 -0
- package/src/components/shared/controls/SLink.css +114 -0
- package/src/components/shared/controls/SLink.ts +85 -0
- package/src/components/shared/controls/SLink.vue +114 -0
- package/src/components/shared/controls/SListbox.css +31 -0
- package/src/components/shared/controls/SListbox.vue +388 -0
- package/src/components/shared/controls/SListboxOption.css +63 -0
- package/src/components/shared/controls/SListboxOption.vue +85 -0
- package/src/components/shared/controls/SMarker.css +19 -0
- package/src/components/shared/controls/SMarker.vue +33 -0
- package/src/components/shared/controls/SMenuSurface.css +20 -0
- package/src/components/shared/controls/SMenuSurface.vue +333 -0
- package/src/components/shared/controls/SSegmentedControl.css +38 -0
- package/src/components/shared/controls/SSegmentedControl.vue +134 -0
- package/src/components/shared/controls/SSelect.css +12 -0
- package/src/components/shared/controls/SSelect.vue +338 -0
- package/src/components/shared/controls/STextarea.css +31 -0
- package/src/components/shared/controls/STextarea.vue +316 -0
- package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +224 -0
- package/src/components/shared/controls/menu.ts +178 -0
- package/src/components/shared/data-display/SActionCard.css +6 -0
- package/src/components/shared/data-display/SActionCard.vue +49 -0
- package/src/components/shared/data-display/SActionList.css +15 -0
- package/src/components/shared/data-display/SActionList.vue +34 -0
- package/src/components/shared/data-display/SActionListItem.css +114 -0
- package/src/components/shared/data-display/SActionListItem.vue +230 -0
- package/src/components/shared/data-display/SBadge.css +73 -0
- package/src/components/shared/data-display/SBadge.vue +52 -0
- package/src/components/shared/data-display/SCodeBlock.css +56 -0
- package/src/components/shared/data-display/SCodeBlock.vue +188 -0
- package/src/components/shared/data-display/SCodeEditor.css +178 -0
- package/src/components/shared/data-display/SCodeEditor.vue +274 -0
- package/src/components/shared/data-display/SCodeSearchPanel.vue +327 -0
- package/src/components/shared/data-display/SCopyField.vue +77 -0
- package/src/components/shared/data-display/SDocBlock.css +136 -0
- package/src/components/shared/data-display/SDocBlock.vue +358 -0
- package/src/components/shared/data-display/SEmptyState.css +42 -0
- package/src/components/shared/data-display/SEmptyState.vue +79 -0
- package/src/components/shared/data-display/SExpandableText.vue +314 -0
- package/src/components/shared/data-display/SJsonTree.css +6 -0
- package/src/components/shared/data-display/SJsonTree.vue +111 -0
- package/src/components/shared/data-display/SKeyValueGrid.css +37 -0
- package/src/components/shared/data-display/SKeyValueGrid.vue +277 -0
- package/src/components/shared/data-display/SLinkedSystemsList.vue +270 -0
- package/src/components/shared/data-display/SMessage.css +88 -0
- package/src/components/shared/data-display/SMessage.vue +152 -0
- package/src/components/shared/data-display/SMetricCard.css +32 -0
- package/src/components/shared/data-display/SMetricCard.vue +117 -0
- package/src/components/shared/data-display/SProgressBar.css +84 -0
- package/src/components/shared/data-display/SProgressBar.vue +114 -0
- package/src/components/shared/data-display/SProgressIndicator.css +59 -0
- package/src/components/shared/data-display/SProgressIndicator.vue +104 -0
- package/src/components/shared/data-display/SSectionHeader.css +84 -0
- package/src/components/shared/data-display/SSectionHeader.vue +58 -0
- package/src/components/shared/data-display/SStatus.css +90 -0
- package/src/components/shared/data-display/SStatus.vue +79 -0
- package/src/components/shared/data-display/STable.css +83 -0
- package/src/components/shared/data-display/STable.vue +339 -0
- package/src/components/shared/data-display/SToastContainer.css +53 -0
- package/src/components/shared/data-display/SToastContainer.vue +31 -0
- package/src/components/shared/data-display/STooltip.css +114 -0
- package/src/components/shared/data-display/STooltip.vue +550 -0
- package/src/components/shared/data-display/STooltipAnchor.css +21 -0
- package/src/components/shared/data-display/STooltipAnchor.vue +119 -0
- package/src/components/shared/data-display/SVirtualList.css +24 -0
- package/src/components/shared/data-display/SVirtualList.ts +19 -0
- package/src/components/shared/data-display/SVirtualList.vue +361 -0
- package/src/components/shared/data-display/json.ts +136 -0
- package/src/components/shared/data-display/table.ts +76 -0
- package/src/components/shared/database/SDataGrid.css +3 -0
- package/src/components/shared/database/SDataGrid.vue +464 -0
- package/src/components/shared/database/SSqlEditor.vue +363 -0
- package/src/components/shared/database/dataGrid.ts +61 -0
- package/src/components/shared/feedback/SAsyncState.css +26 -0
- package/src/components/shared/feedback/SAsyncState.vue +190 -0
- package/src/components/shared/graph/SGraphViewport.ts +1 -0
- package/src/components/shared/graph/SGraphViewport.vue +858 -0
- package/src/components/shared/graph/_internal/graphViewportContract.ts +362 -0
- package/src/components/shared/markdown.ts +169 -0
- package/src/components/shared/navigation/SBottomNav.vue +692 -0
- package/src/components/shared/navigation/SBreadcrumbs.css +26 -0
- package/src/components/shared/navigation/SBreadcrumbs.vue +155 -0
- package/src/components/shared/navigation/SCatalogNavigator.css +100 -0
- package/src/components/shared/navigation/SCatalogNavigator.vue +308 -0
- package/src/components/shared/navigation/STab.vue +363 -0
- package/src/components/shared/navigation/STabList.vue +442 -0
- package/src/components/shared/navigation/STabPanel.vue +165 -0
- package/src/components/shared/navigation/STabPanels.vue +33 -0
- package/src/components/shared/navigation/STabs.vue +617 -0
- package/src/components/shared/navigation/SWizardSteps.css +69 -0
- package/src/components/shared/navigation/SWizardSteps.vue +171 -0
- package/src/components/shared/navigation/_internal/SCatalogNavigatorGroup.vue +63 -0
- package/src/components/shared/persona/SPersonaProjectPicker.css +36 -0
- package/src/components/shared/persona/SPersonaProjectPicker.vue +361 -0
- package/src/components/shared/persona/SPersonaRunWorkbench.css +109 -0
- package/src/components/shared/persona/SPersonaRunWorkbench.vue +549 -0
- package/src/components/shared/persona/_internal/SPersonaRunConsole.css +40 -0
- package/src/components/shared/persona/_internal/SPersonaRunConsole.vue +298 -0
- package/src/components/shared/persona/_internal/inventory.ts +110 -0
- package/src/components/shared/persona/_internal/labels.ts +79 -0
- package/src/components/shared/persona/presentation.ts +371 -0
- package/src/components/shared/persona/types.ts +256 -0
- package/src/composables/uiPreferencesReset.ts +52 -0
- package/src/composables/useClipboard.ts +37 -0
- package/src/composables/useDialogOverlay.ts +85 -0
- package/src/composables/useDisclosure.ts +40 -0
- package/src/composables/useFloatingPosition.ts +245 -0
- package/src/composables/useNotifier.ts +26 -0
- package/src/composables/useSidebarPanelState.ts +610 -0
- package/src/internal/codeEditorContract.ts +110 -0
- package/src/internal/codeEditorExtensions.ts +27 -0
- package/src/internal/feedbackPresentation.ts +136 -0
- package/src/internal/fieldSurface.ts +59 -0
- package/src/internal/focusNavigation.ts +69 -0
- package/src/internal/inlineTokenEditorContract.ts +136 -0
- package/src/internal/interactiveElement.ts +33 -0
- package/src/internal/layerStack.ts +202 -0
- package/src/internal/linkTarget.ts +256 -0
- package/src/internal/ownedAttrs.ts +464 -0
- package/src/internal/passiveContentContract.ts +129 -0
- package/src/internal/pointerInteractionLease.ts +81 -0
- package/src/internal/runtimeContract.ts +194 -0
- package/src/internal/runtimeTheme.ts +159 -0
- package/src/internal/semanticSizing.ts +60 -0
- package/src/internal/useMenuLayer.ts +129 -0
- package/src/stores/useNotifierStore.ts +69 -0
- package/src/styles/reference.css +4 -0
- package/src/styles/style.css +151 -0
- package/src/styles/tokens.css +836 -0
- package/src/theme.ts +449 -0
- package/src/types/highlight-js-lib.d.ts +76 -0
- package/src/variants.ts +15 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-bind="ownedAttrs.bindings()"
|
|
4
|
+
class="s-tabs"
|
|
5
|
+
:data-testid="testId"
|
|
6
|
+
:data-composition="composition"
|
|
7
|
+
:data-variant="variant"
|
|
8
|
+
:data-grow="grow"
|
|
9
|
+
:data-density="tabDensity"
|
|
10
|
+
:data-sizing="tabSizing"
|
|
11
|
+
:data-list-width="tabListWidth"
|
|
12
|
+
:data-tone="tone"
|
|
13
|
+
:data-close-mode="closeButtonMode"
|
|
14
|
+
:data-radius="surfaceRadius"
|
|
15
|
+
:data-surface="surface"
|
|
16
|
+
:data-has-panels="composition === 'custom' ? 'custom' : ($slots.panels ? 'true' : 'false')"
|
|
17
|
+
>
|
|
18
|
+
<slot v-if="composition === 'custom'" />
|
|
19
|
+
<template v-else>
|
|
20
|
+
<STabList
|
|
21
|
+
v-if="variant !== 'bottom'"
|
|
22
|
+
placement="top"
|
|
23
|
+
:aria-label="ariaLabel"
|
|
24
|
+
:controls-aria-label="controlsAriaLabel"
|
|
25
|
+
:header-position="headerPosition"
|
|
26
|
+
:inset="tabListInset"
|
|
27
|
+
>
|
|
28
|
+
<slot name="list" />
|
|
29
|
+
<template v-if="$slots.controls" #controls><slot name="controls" /></template>
|
|
30
|
+
</STabList>
|
|
31
|
+
<STabPanels v-if="$slots.panels"><slot name="panels" /></STabPanels>
|
|
32
|
+
<STabList
|
|
33
|
+
v-if="variant === 'bottom'"
|
|
34
|
+
placement="bottom"
|
|
35
|
+
:aria-label="ariaLabel"
|
|
36
|
+
:controls-aria-label="controlsAriaLabel"
|
|
37
|
+
>
|
|
38
|
+
<slot name="list" />
|
|
39
|
+
<template v-if="$slots.controls" #controls><slot name="controls" /></template>
|
|
40
|
+
</STabList>
|
|
41
|
+
</template>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
import {
|
|
47
|
+
computed,
|
|
48
|
+
nextTick,
|
|
49
|
+
onBeforeUnmount,
|
|
50
|
+
onMounted,
|
|
51
|
+
onUnmounted,
|
|
52
|
+
provide,
|
|
53
|
+
ref,
|
|
54
|
+
shallowRef,
|
|
55
|
+
useId,
|
|
56
|
+
watch,
|
|
57
|
+
type VNodeChild,
|
|
58
|
+
} from 'vue'
|
|
59
|
+
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
60
|
+
import {
|
|
61
|
+
tabsContextKey,
|
|
62
|
+
type STabListRegistrationKey,
|
|
63
|
+
type STabPanelRegistration,
|
|
64
|
+
type STabPanelRegistrationKey,
|
|
65
|
+
type STabPanelsRegistrationKey,
|
|
66
|
+
type STabPanelState,
|
|
67
|
+
type STabRegistration,
|
|
68
|
+
type STabRegistrationKey,
|
|
69
|
+
type STabState,
|
|
70
|
+
type STabsContext,
|
|
71
|
+
} from '../_internal/tabsContext'
|
|
72
|
+
import STabList from './STabList.vue'
|
|
73
|
+
import STabPanels from './STabPanels.vue'
|
|
74
|
+
|
|
75
|
+
defineOptions({ inheritAttrs: false })
|
|
76
|
+
|
|
77
|
+
export type STabsComposition = 'managed' | 'custom'
|
|
78
|
+
export type STabsVariant = 'default' | 'flat' | 'bottom' | 'icon-only' | 'icon-with-label'
|
|
79
|
+
export type STabsActivationMode = 'automatic' | 'manual'
|
|
80
|
+
export type STabsTone = 'primary' | 'success'
|
|
81
|
+
export type STabsSizing =
|
|
82
|
+
| 'content'
|
|
83
|
+
| 'compact'
|
|
84
|
+
| 'standard'
|
|
85
|
+
| 'wide'
|
|
86
|
+
| 'equal'
|
|
87
|
+
| 'columns-2'
|
|
88
|
+
| 'columns-3'
|
|
89
|
+
| 'columns-4'
|
|
90
|
+
|
|
91
|
+
export interface Props {
|
|
92
|
+
/** Стабильный public test hook owner-поверхности. / Stable public test hook for the owner surface. */
|
|
93
|
+
testId?: string
|
|
94
|
+
/** Managed slots или явная compound composition. / Managed slots or explicit compound composition. */
|
|
95
|
+
composition?: STabsComposition
|
|
96
|
+
/** Визуальная поверхность набора вкладок. / Visual surface of the tab set. */
|
|
97
|
+
variant?: STabsVariant
|
|
98
|
+
/** Управляемый стабильный ключ. / Controlled stable key. */
|
|
99
|
+
modelValue?: string
|
|
100
|
+
/** Начальный стабильный ключ для uncontrolled режима. / Initial stable key for uncontrolled mode. */
|
|
101
|
+
defaultValue?: string
|
|
102
|
+
/** APG activation policy при roving focus. / APG activation policy during roving focus. */
|
|
103
|
+
activationMode?: STabsActivationMode
|
|
104
|
+
/** Семантический цвет selection. / Semantic selection tone. */
|
|
105
|
+
tone?: STabsTone
|
|
106
|
+
/** Accessible name единственного tablist. / Accessible name of the single tablist. */
|
|
107
|
+
ariaLabel?: string
|
|
108
|
+
/** Accessible name внешней action region. / Accessible name of the external action region. */
|
|
109
|
+
controlsAriaLabel?: string
|
|
110
|
+
/** Sticky/static политика managed header. / Sticky/static policy of the managed header. */
|
|
111
|
+
headerPosition?: 'sticky' | 'static'
|
|
112
|
+
/** Семантический inline inset tab row. / Semantic inline inset of the tab row. */
|
|
113
|
+
tabListInset?: 'none' | 'compact' | 'content'
|
|
114
|
+
/** Разрешает owner занимать доступную высоту. / Lets the owner fill available height. */
|
|
115
|
+
grow?: boolean
|
|
116
|
+
/** Семантическая геометрия без произвольных CSS lengths. / Semantic geometry without arbitrary CSS lengths. */
|
|
117
|
+
tabSizing?: STabsSizing
|
|
118
|
+
/** Семантическая ширина tab collection. / Semantic width of the tab collection. */
|
|
119
|
+
tabListWidth?: 'auto' | 'content' | 'full'
|
|
120
|
+
/** Плотность tab hitbox. / Density of the tab hitbox. */
|
|
121
|
+
tabDensity?: 'compact' | 'default' | 'comfortable'
|
|
122
|
+
/** Резервирует или перекрывает место sibling close action. / Reserves or overlays sibling close-action space. */
|
|
123
|
+
closeButtonMode?: 'reserve' | 'overlay'
|
|
124
|
+
/** Скругление owner surface. / Radius of the owner surface. */
|
|
125
|
+
surfaceRadius?: 'default' | 'none'
|
|
126
|
+
/** Фон owner surface. / Background of the owner surface. */
|
|
127
|
+
surface?: 'default' | 'transparent'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface TabRecord extends STabRegistration {
|
|
131
|
+
readonly sequence: number
|
|
132
|
+
readonly tabId: string
|
|
133
|
+
readonly panelId: string
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface PanelRecord extends STabPanelRegistration {
|
|
137
|
+
readonly sequence: number
|
|
138
|
+
readonly tabId: string
|
|
139
|
+
readonly panelId: string
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
143
|
+
testId: undefined,
|
|
144
|
+
composition: 'managed',
|
|
145
|
+
variant: 'default',
|
|
146
|
+
modelValue: undefined,
|
|
147
|
+
defaultValue: undefined,
|
|
148
|
+
activationMode: 'automatic',
|
|
149
|
+
tone: 'primary',
|
|
150
|
+
ariaLabel: 'Вкладки',
|
|
151
|
+
controlsAriaLabel: 'Действия вкладок',
|
|
152
|
+
headerPosition: 'sticky',
|
|
153
|
+
tabListInset: 'none',
|
|
154
|
+
grow: true,
|
|
155
|
+
tabSizing: 'standard',
|
|
156
|
+
tabListWidth: 'auto',
|
|
157
|
+
tabDensity: 'default',
|
|
158
|
+
closeButtonMode: 'reserve',
|
|
159
|
+
surfaceRadius: 'default',
|
|
160
|
+
surface: 'default',
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const emit = defineEmits<{
|
|
164
|
+
'update:modelValue': [key: string]
|
|
165
|
+
change: [key: string]
|
|
166
|
+
}>()
|
|
167
|
+
const slots = defineSlots<{
|
|
168
|
+
default?: () => VNodeChild
|
|
169
|
+
list?: () => VNodeChild
|
|
170
|
+
panels?: () => VNodeChild
|
|
171
|
+
controls?: () => VNodeChild
|
|
172
|
+
}>()
|
|
173
|
+
const ownedAttrs = useOwnedAttrs({ component: 'STabs', owner: 'tabs state and layout root' })
|
|
174
|
+
|
|
175
|
+
function assertStableKey(value: unknown, coordinate: string): asserts value is string {
|
|
176
|
+
if (typeof value !== 'string' || !value.trim() || value !== value.trim()) {
|
|
177
|
+
throw new TypeError(`STabs: ${coordinate} must be a non-empty string without surrounding whitespace`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function assertCompositionContract(): void {
|
|
182
|
+
if (props.composition === 'custom' && !slots.default) {
|
|
183
|
+
throw new Error('STabs: composition="custom" requires the default compound slot')
|
|
184
|
+
}
|
|
185
|
+
if (props.composition === 'custom' && (slots.list || slots.panels || slots.controls)) {
|
|
186
|
+
throw new Error('STabs: composition="custom" does not accept managed list/panels/controls slots')
|
|
187
|
+
}
|
|
188
|
+
if (props.composition === 'managed' && !slots.list) {
|
|
189
|
+
throw new Error('STabs: composition="managed" requires the list slot')
|
|
190
|
+
}
|
|
191
|
+
if (props.composition === 'managed' && slots.default) {
|
|
192
|
+
throw new Error('STabs: composition="managed" does not accept the default slot')
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function assertPropsContract(): void {
|
|
197
|
+
assertCompositionContract()
|
|
198
|
+
if (props.modelValue !== undefined) assertStableKey(props.modelValue, 'modelValue')
|
|
199
|
+
if (props.defaultValue !== undefined) assertStableKey(props.defaultValue, 'defaultValue')
|
|
200
|
+
if (props.modelValue !== undefined && props.defaultValue !== undefined) {
|
|
201
|
+
throw new Error('STabs: modelValue and defaultValue are mutually exclusive')
|
|
202
|
+
}
|
|
203
|
+
if (!props.ariaLabel.trim()) throw new TypeError('STabs: ariaLabel must be non-empty')
|
|
204
|
+
if (!props.controlsAriaLabel.trim()) throw new TypeError('STabs: controlsAriaLabel must be non-empty')
|
|
205
|
+
if (props.testId !== undefined && !props.testId.trim()) throw new TypeError('STabs: testId must be non-empty')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
assertPropsContract()
|
|
209
|
+
watch(() => [
|
|
210
|
+
props.composition,
|
|
211
|
+
props.modelValue,
|
|
212
|
+
props.defaultValue,
|
|
213
|
+
props.ariaLabel,
|
|
214
|
+
props.controlsAriaLabel,
|
|
215
|
+
props.testId,
|
|
216
|
+
] as const, assertPropsContract)
|
|
217
|
+
|
|
218
|
+
const ownerId = `s-tabs-${useId()}`
|
|
219
|
+
const tabs = shallowRef<TabRecord[]>([])
|
|
220
|
+
const panels = shallowRef<PanelRecord[]>([])
|
|
221
|
+
const panelOwners = new Set<STabPanelsRegistrationKey>()
|
|
222
|
+
const tabLists = new Map<STabListRegistrationKey, () => void>()
|
|
223
|
+
const identityOrdinals = new Map<string, number>()
|
|
224
|
+
const orderVersion = ref(0)
|
|
225
|
+
const internalActiveKey = ref<string | null>(props.defaultValue ?? null)
|
|
226
|
+
const rovingTabKey = ref<string | null>(null)
|
|
227
|
+
let nextIdentityOrdinal = 0
|
|
228
|
+
let nextSequence = 0
|
|
229
|
+
let inventoryRevision = 0
|
|
230
|
+
let isMounted = false
|
|
231
|
+
let isUnmounting = false
|
|
232
|
+
|
|
233
|
+
const isControlled = computed(() => props.modelValue !== undefined)
|
|
234
|
+
const activeKey = computed<string | null>(() => (
|
|
235
|
+
isControlled.value ? (props.modelValue ?? null) : internalActiveKey.value
|
|
236
|
+
))
|
|
237
|
+
const activationMode = computed(() => props.activationMode)
|
|
238
|
+
const closeMode = computed(() => props.closeButtonMode)
|
|
239
|
+
const density = computed(() => props.tabDensity)
|
|
240
|
+
|
|
241
|
+
function identityIds(tabKey: string): { tabId: string; panelId: string } {
|
|
242
|
+
let ordinal = identityOrdinals.get(tabKey)
|
|
243
|
+
if (ordinal === undefined) {
|
|
244
|
+
ordinal = nextIdentityOrdinal
|
|
245
|
+
nextIdentityOrdinal += 1
|
|
246
|
+
identityOrdinals.set(tabKey, ordinal)
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
tabId: `${ownerId}-tab-${ordinal}`,
|
|
250
|
+
panelId: `${ownerId}-panel-${ordinal}`,
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function compareElements(
|
|
255
|
+
left: { element: () => HTMLElement | null; sequence: number },
|
|
256
|
+
right: { element: () => HTMLElement | null; sequence: number },
|
|
257
|
+
): number {
|
|
258
|
+
const leftElement = left.element()
|
|
259
|
+
const rightElement = right.element()
|
|
260
|
+
if (!leftElement || !rightElement || leftElement === rightElement) return left.sequence - right.sequence
|
|
261
|
+
const position = leftElement.compareDocumentPosition(rightElement)
|
|
262
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1
|
|
263
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1
|
|
264
|
+
return left.sequence - right.sequence
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const orderedTabs = computed(() => {
|
|
268
|
+
void orderVersion.value
|
|
269
|
+
return [...tabs.value].sort(compareElements)
|
|
270
|
+
})
|
|
271
|
+
const orderedPanels = computed(() => {
|
|
272
|
+
void orderVersion.value
|
|
273
|
+
return [...panels.value].sort(compareElements)
|
|
274
|
+
})
|
|
275
|
+
const activeTab = computed(() => (
|
|
276
|
+
activeKey.value === null ? null : orderedTabs.value.find((tab) => tab.tabKey === activeKey.value) ?? null
|
|
277
|
+
))
|
|
278
|
+
const effectiveRovingKey = computed(() => {
|
|
279
|
+
const roving = orderedTabs.value.find((tab) => tab.tabKey === rovingTabKey.value && !tab.disabled())
|
|
280
|
+
if (roving) return roving.tabKey
|
|
281
|
+
if (activeTab.value && !activeTab.value.disabled()) return activeTab.value.tabKey
|
|
282
|
+
return orderedTabs.value.find((tab) => !tab.disabled())?.tabKey ?? null
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
function panelsRequired(): boolean {
|
|
286
|
+
return Boolean(slots.panels) || panelOwners.size > 0
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function validateInventory(): void {
|
|
290
|
+
if (isUnmounting) return
|
|
291
|
+
if (tabLists.size !== 1) {
|
|
292
|
+
throw new Error(`STabs: expected exactly one STabList owner, received ${tabLists.size}`)
|
|
293
|
+
}
|
|
294
|
+
const tabKeys = new Set(orderedTabs.value.map((tab) => tab.tabKey))
|
|
295
|
+
const panelKeys = new Set(orderedPanels.value.map((panel) => panel.tabKey))
|
|
296
|
+
if (panelsRequired()) {
|
|
297
|
+
for (const key of tabKeys) {
|
|
298
|
+
if (!panelKeys.has(key)) throw new Error(`STabs: missing panel for tabKey "${key}"`)
|
|
299
|
+
}
|
|
300
|
+
for (const key of panelKeys) {
|
|
301
|
+
if (!tabKeys.has(key)) throw new Error(`STabs: orphan panel for tabKey "${key}"`)
|
|
302
|
+
}
|
|
303
|
+
} else if (panelKeys.size > 0) {
|
|
304
|
+
throw new Error('STabs: panels require exactly one STabPanels owner')
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (activeKey.value !== null) {
|
|
308
|
+
const selected = orderedTabs.value.find((tab) => tab.tabKey === activeKey.value)
|
|
309
|
+
if (!selected) throw new Error(`STabs: stale selected tabKey "${activeKey.value}"`)
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
if (!isControlled.value) {
|
|
313
|
+
internalActiveKey.value = orderedTabs.value.find((tab) => !tab.disabled())?.tabKey ?? null
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function scheduleInventoryValidation(): void {
|
|
318
|
+
inventoryRevision += 1
|
|
319
|
+
const revision = inventoryRevision
|
|
320
|
+
if (!isMounted || isUnmounting) return
|
|
321
|
+
void nextTick(() => {
|
|
322
|
+
if (revision === inventoryRevision) validateInventory()
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function registerTab(registration: STabRegistration): STabRegistrationKey {
|
|
327
|
+
assertStableKey(registration.tabKey, 'STab.name')
|
|
328
|
+
if (tabs.value.some((tab) => tab.tabKey === registration.tabKey)) {
|
|
329
|
+
throw new Error(`STabs: duplicate tabKey "${registration.tabKey}"`)
|
|
330
|
+
}
|
|
331
|
+
const ids = identityIds(registration.tabKey)
|
|
332
|
+
tabs.value = [...tabs.value, { ...registration, ...ids, sequence: nextSequence++ }]
|
|
333
|
+
orderVersion.value += 1
|
|
334
|
+
scheduleInventoryValidation()
|
|
335
|
+
return registration.tabKey
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function unregisterTab(key: STabRegistrationKey): void {
|
|
339
|
+
if (!tabs.value.some((tab) => tab.tabKey === key)) throw new Error(`STabs: unknown tab registration "${key}"`)
|
|
340
|
+
tabs.value = tabs.value.filter((tab) => tab.tabKey !== key)
|
|
341
|
+
if (rovingTabKey.value === key) rovingTabKey.value = null
|
|
342
|
+
orderVersion.value += 1
|
|
343
|
+
scheduleInventoryValidation()
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function registerPanel(registration: STabPanelRegistration): STabPanelRegistrationKey {
|
|
347
|
+
assertStableKey(registration.tabKey, 'STabPanel.name')
|
|
348
|
+
if (panels.value.some((panel) => panel.tabKey === registration.tabKey)) {
|
|
349
|
+
throw new Error(`STabs: duplicate panel tabKey "${registration.tabKey}"`)
|
|
350
|
+
}
|
|
351
|
+
const ids = identityIds(registration.tabKey)
|
|
352
|
+
panels.value = [...panels.value, { ...registration, ...ids, sequence: nextSequence++ }]
|
|
353
|
+
orderVersion.value += 1
|
|
354
|
+
scheduleInventoryValidation()
|
|
355
|
+
return registration.tabKey
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function unregisterPanel(key: STabPanelRegistrationKey): void {
|
|
359
|
+
if (!panels.value.some((panel) => panel.tabKey === key)) throw new Error(`STabs: unknown panel registration "${key}"`)
|
|
360
|
+
panels.value = panels.value.filter((panel) => panel.tabKey !== key)
|
|
361
|
+
orderVersion.value += 1
|
|
362
|
+
scheduleInventoryValidation()
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function getTabState(key: STabRegistrationKey): STabState {
|
|
366
|
+
const tab = tabs.value.find((candidate) => candidate.tabKey === key)
|
|
367
|
+
if (!tab) throw new Error(`STabs: missing tab registration "${key}"`)
|
|
368
|
+
const hasPanel = panelsRequired() || panels.value.some((panel) => panel.tabKey === key)
|
|
369
|
+
return {
|
|
370
|
+
active: activeKey.value === key,
|
|
371
|
+
disabled: tab.disabled(),
|
|
372
|
+
tabIndex: effectiveRovingKey.value === key ? 0 : -1,
|
|
373
|
+
tabId: tab.tabId,
|
|
374
|
+
panelId: hasPanel ? tab.panelId : undefined,
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function getActiveTabElement(): HTMLElement | null {
|
|
379
|
+
return activeTab.value?.element() ?? null
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function getPanelState(key: STabPanelRegistrationKey): STabPanelState {
|
|
383
|
+
const panel = panels.value.find((candidate) => candidate.tabKey === key)
|
|
384
|
+
if (!panel) throw new Error(`STabs: missing panel registration "${key}"`)
|
|
385
|
+
const hasTab = tabs.value.some((tab) => tab.tabKey === key)
|
|
386
|
+
return {
|
|
387
|
+
active: activeKey.value === key,
|
|
388
|
+
panelId: panel.panelId,
|
|
389
|
+
tabId: hasTab ? panel.tabId : undefined,
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function activateTab(key: STabRegistrationKey): void {
|
|
394
|
+
const tab = orderedTabs.value.find((candidate) => candidate.tabKey === key)
|
|
395
|
+
if (!tab) throw new Error(`STabs: cannot activate missing tabKey "${key}"`)
|
|
396
|
+
if (tab.disabled()) return
|
|
397
|
+
rovingTabKey.value = key
|
|
398
|
+
if (activeKey.value === key) return
|
|
399
|
+
if (!isControlled.value) internalActiveKey.value = key
|
|
400
|
+
emit('update:modelValue', key)
|
|
401
|
+
emit('change', key)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function focusTab(key: STabRegistrationKey): void {
|
|
405
|
+
const tab = orderedTabs.value.find((candidate) => candidate.tabKey === key)
|
|
406
|
+
if (!tab) throw new Error(`STabs: cannot focus missing tabKey "${key}"`)
|
|
407
|
+
if (!tab.disabled()) rovingTabKey.value = key
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function handleTabKeydown(key: STabRegistrationKey, event: KeyboardEvent): void {
|
|
411
|
+
const current = orderedTabs.value.find((tab) => tab.tabKey === key)
|
|
412
|
+
if (!current) throw new Error(`STabs: cannot handle keyboard input for missing tabKey "${key}"`)
|
|
413
|
+
if (current.disabled()) return
|
|
414
|
+
const enabled = orderedTabs.value.filter((tab) => !tab.disabled())
|
|
415
|
+
const currentIndex = enabled.findIndex((tab) => tab.tabKey === current.tabKey)
|
|
416
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
417
|
+
event.preventDefault()
|
|
418
|
+
activateTab(current.tabKey)
|
|
419
|
+
return
|
|
420
|
+
}
|
|
421
|
+
let next: TabRecord | undefined
|
|
422
|
+
if (event.key === 'Home') next = enabled[0]
|
|
423
|
+
else if (event.key === 'End') next = enabled.at(-1)
|
|
424
|
+
else if (event.key === 'ArrowLeft') next = enabled[(currentIndex - 1 + enabled.length) % enabled.length]
|
|
425
|
+
else if (event.key === 'ArrowRight') next = enabled[(currentIndex + 1) % enabled.length]
|
|
426
|
+
if (!next) return
|
|
427
|
+
event.preventDefault()
|
|
428
|
+
rovingTabKey.value = next.tabKey
|
|
429
|
+
next.element()?.focus()
|
|
430
|
+
if (activationMode.value === 'automatic') activateTab(next.tabKey)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function registerTabList(scroll: () => void): STabListRegistrationKey {
|
|
434
|
+
if (tabLists.size > 0) throw new Error('STabs: exactly one STabList owner is allowed')
|
|
435
|
+
const key = Symbol('tab-list')
|
|
436
|
+
tabLists.set(key, scroll)
|
|
437
|
+
scheduleInventoryValidation()
|
|
438
|
+
return key
|
|
439
|
+
}
|
|
440
|
+
function unregisterTabList(key: STabListRegistrationKey): void {
|
|
441
|
+
if (!tabLists.delete(key)) throw new Error('STabs: unknown tablist registration')
|
|
442
|
+
}
|
|
443
|
+
function registerPanels(): STabPanelsRegistrationKey {
|
|
444
|
+
if (panelOwners.size > 0) throw new Error('STabs: exactly one STabPanels owner is allowed')
|
|
445
|
+
const key = Symbol('tab-panels')
|
|
446
|
+
panelOwners.add(key)
|
|
447
|
+
scheduleInventoryValidation()
|
|
448
|
+
return key
|
|
449
|
+
}
|
|
450
|
+
function unregisterPanels(key: STabPanelsRegistrationKey): void {
|
|
451
|
+
if (!panelOwners.delete(key)) throw new Error('STabs: unknown STabPanels registration')
|
|
452
|
+
scheduleInventoryValidation()
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const context: STabsContext = {
|
|
456
|
+
activeKey,
|
|
457
|
+
activationMode,
|
|
458
|
+
closeButtonMode: closeMode,
|
|
459
|
+
tabDensity: density,
|
|
460
|
+
registerTab,
|
|
461
|
+
unregisterTab,
|
|
462
|
+
notifyTabOrderChanged: () => { orderVersion.value += 1 },
|
|
463
|
+
getTabState,
|
|
464
|
+
getActiveTabElement,
|
|
465
|
+
activateTab,
|
|
466
|
+
focusTab,
|
|
467
|
+
registerPanel,
|
|
468
|
+
unregisterPanel,
|
|
469
|
+
notifyPanelOrderChanged: () => { orderVersion.value += 1 },
|
|
470
|
+
getPanelState,
|
|
471
|
+
handleTabKeydown,
|
|
472
|
+
registerTabList,
|
|
473
|
+
unregisterTabList,
|
|
474
|
+
registerPanels,
|
|
475
|
+
unregisterPanels,
|
|
476
|
+
}
|
|
477
|
+
provide(tabsContextKey, context)
|
|
478
|
+
|
|
479
|
+
watch(() => props.modelValue, () => {
|
|
480
|
+
rovingTabKey.value = null
|
|
481
|
+
scheduleInventoryValidation()
|
|
482
|
+
})
|
|
483
|
+
watch(() => tabs.value.map((tab) => [tab.tabKey, tab.disabled()] as const), scheduleInventoryValidation, { deep: true })
|
|
484
|
+
|
|
485
|
+
onMounted(() => {
|
|
486
|
+
isMounted = true
|
|
487
|
+
try {
|
|
488
|
+
validateInventory()
|
|
489
|
+
} catch (error) {
|
|
490
|
+
isUnmounting = true
|
|
491
|
+
inventoryRevision += 1
|
|
492
|
+
throw error
|
|
493
|
+
}
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
onBeforeUnmount(() => {
|
|
497
|
+
isUnmounting = true
|
|
498
|
+
inventoryRevision += 1
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
onUnmounted(() => {
|
|
502
|
+
tabs.value = []
|
|
503
|
+
panels.value = []
|
|
504
|
+
tabLists.clear()
|
|
505
|
+
panelOwners.clear()
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
defineExpose({
|
|
509
|
+
scrollActiveIntoCenter: () => {
|
|
510
|
+
for (const scroll of tabLists.values()) scroll()
|
|
511
|
+
},
|
|
512
|
+
})
|
|
513
|
+
</script>
|
|
514
|
+
|
|
515
|
+
<style lang="postcss" scoped>
|
|
516
|
+
@reference "../../../styles/reference.css";
|
|
517
|
+
|
|
518
|
+
.s-tabs {
|
|
519
|
+
@apply relative flex h-full min-h-0 min-w-0 w-full flex-col rounded-lg border border-surface-200 bg-surface-0;
|
|
520
|
+
@apply dark:border-surface-700 dark:bg-surface-800;
|
|
521
|
+
--s-tab-gap: 0.5rem;
|
|
522
|
+
--s-tab-base-padding-inline: 1rem;
|
|
523
|
+
--s-tab-padding-inline: var(--s-tab-base-padding-inline);
|
|
524
|
+
--s-tab-padding-block: 0;
|
|
525
|
+
--s-tab-content-offset-y: 2px;
|
|
526
|
+
--s-tab-trigger-min-height: var(--s-control-hitbox-sm);
|
|
527
|
+
--s-tab-indicator-width: 2px;
|
|
528
|
+
--s-tab-radius: 0.375rem 0.375rem 0 0;
|
|
529
|
+
--s-tab-active-translate-y: 1px;
|
|
530
|
+
--s-tab-disabled-opacity: 0.58;
|
|
531
|
+
--s-tab-sibling-actions-gap: 0.25rem;
|
|
532
|
+
--s-tab-direction: row;
|
|
533
|
+
--s-tab-label-display: inline-block;
|
|
534
|
+
--s-tab-label-flex: 1 1 auto;
|
|
535
|
+
--s-tab-label-size: inherit;
|
|
536
|
+
--s-tab-label-line-height: 1.25rem;
|
|
537
|
+
--s-tab-panels-flex: 1 1 0%;
|
|
538
|
+
--s-tab-row-height: 2.5rem;
|
|
539
|
+
--s-tab-width: auto;
|
|
540
|
+
--s-tab-min-width: 0;
|
|
541
|
+
--s-tab-max-width: 15rem;
|
|
542
|
+
--s-tab-flex: 0 0 auto;
|
|
543
|
+
--s-tab-list-tabs-width: auto;
|
|
544
|
+
--s-tab-list-inset-inline: 0;
|
|
545
|
+
--s-tab-overflow-fade-width: 3rem;
|
|
546
|
+
--s-tab-overflow-edge-opacity: 0.96;
|
|
547
|
+
--s-tab-list-edge-surface: var(--color-surface-0);
|
|
548
|
+
--s-tab-color: var(--color-surface-500);
|
|
549
|
+
--s-tab-hover-color: var(--color-surface-700);
|
|
550
|
+
--s-tab-hover-background: var(--color-surface-100);
|
|
551
|
+
--s-tab-selected-color: var(--color-primary-600);
|
|
552
|
+
--s-tab-selected-border-color: var(--color-primary-500);
|
|
553
|
+
--s-tab-disabled-color: var(--color-surface-400);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
:global(.dark) .s-tabs {
|
|
557
|
+
--s-tab-list-edge-surface: var(--color-surface-800);
|
|
558
|
+
--s-tab-color: var(--color-surface-400);
|
|
559
|
+
--s-tab-hover-color: var(--color-surface-200);
|
|
560
|
+
--s-tab-hover-background: var(--color-surface-800);
|
|
561
|
+
--s-tab-selected-color: var(--color-primary-400);
|
|
562
|
+
--s-tab-selected-border-color: var(--color-primary-400);
|
|
563
|
+
--s-tab-disabled-color: var(--color-surface-600);
|
|
564
|
+
}
|
|
565
|
+
.s-tabs[data-tone='success'] {
|
|
566
|
+
--s-tab-hover-color: var(--color-success-700);
|
|
567
|
+
--s-tab-selected-color: var(--color-success-700);
|
|
568
|
+
--s-tab-selected-border-color: var(--color-success-600);
|
|
569
|
+
}
|
|
570
|
+
:global(.dark) .s-tabs[data-tone='success'] {
|
|
571
|
+
--s-tab-hover-color: var(--color-success-300);
|
|
572
|
+
--s-tab-selected-color: var(--color-success-300);
|
|
573
|
+
--s-tab-selected-border-color: var(--color-success-400);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.s-tabs[data-density='compact'] { --s-tab-row-height: 2rem; --s-tab-base-padding-inline: 0.5rem; }
|
|
577
|
+
.s-tabs[data-density='comfortable'] { --s-tab-row-height: 3rem; --s-tab-base-padding-inline: 1.125rem; }
|
|
578
|
+
.s-tabs[data-sizing='content'] { --s-tab-min-width: 7rem; --s-tab-max-width: none; }
|
|
579
|
+
.s-tabs[data-sizing='compact'] { --s-tab-max-width: 10.5rem; }
|
|
580
|
+
.s-tabs[data-sizing='wide'] { --s-tab-max-width: 20rem; }
|
|
581
|
+
.s-tabs[data-sizing='equal'] {
|
|
582
|
+
--s-tab-width: 0;
|
|
583
|
+
--s-tab-min-width: 0;
|
|
584
|
+
--s-tab-max-width: none;
|
|
585
|
+
--s-tab-flex: 1 1 0;
|
|
586
|
+
}
|
|
587
|
+
.s-tabs[data-sizing='columns-2'],
|
|
588
|
+
.s-tabs[data-sizing='columns-3'],
|
|
589
|
+
.s-tabs[data-sizing='columns-4'] {
|
|
590
|
+
--s-tab-min-width: 0;
|
|
591
|
+
--s-tab-max-width: none;
|
|
592
|
+
}
|
|
593
|
+
.s-tabs[data-sizing='columns-2'] { --s-tab-width: 50%; }
|
|
594
|
+
.s-tabs[data-sizing='columns-3'] { --s-tab-width: 33.3333%; }
|
|
595
|
+
.s-tabs[data-sizing='columns-4'] { --s-tab-width: 25%; }
|
|
596
|
+
.s-tabs[data-list-width='content'] { --s-tab-list-tabs-width: max-content; }
|
|
597
|
+
.s-tabs[data-list-width='full'] { --s-tab-list-tabs-width: 100%; }
|
|
598
|
+
.s-tabs[data-radius='none'] { @apply rounded-none; }
|
|
599
|
+
.s-tabs[data-surface='transparent'] { background: transparent; }
|
|
600
|
+
.s-tabs[data-grow='false'] { @apply h-auto; --s-tab-panels-flex: none; }
|
|
601
|
+
.s-tabs[data-variant='flat'] { @apply rounded-none border-0; }
|
|
602
|
+
.s-tabs[data-variant='bottom'] {
|
|
603
|
+
@apply fixed inset-x-0 bottom-0 rounded-none border-t bg-surface-0 dark:bg-surface-800;
|
|
604
|
+
z-index: var(--s-layer-navigation);
|
|
605
|
+
--s-tab-content-offset-y: 0;
|
|
606
|
+
}
|
|
607
|
+
.s-tabs[data-variant='bottom'][data-has-panels='false'] { @apply h-auto; }
|
|
608
|
+
.s-tabs[data-variant='icon-only'] { --s-tab-label-display: none; }
|
|
609
|
+
.s-tabs[data-variant='icon-with-label'] {
|
|
610
|
+
--s-tab-label-size: 12px;
|
|
611
|
+
--s-tab-label-flex: 0 1 auto;
|
|
612
|
+
--s-tab-direction: column;
|
|
613
|
+
--s-tab-padding-block: 0.5rem;
|
|
614
|
+
--s-tab-content-offset-y: 0;
|
|
615
|
+
--s-tab-gap: 0.25rem;
|
|
616
|
+
}
|
|
617
|
+
</style>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
.s-wizard-steps {
|
|
2
|
+
min-width: 0;
|
|
3
|
+
border-radius: var(--radius-lg);
|
|
4
|
+
background: var(--s-wizard-steps-surface);
|
|
5
|
+
padding: var(--s-wizard-steps-padding);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.s-wizard-steps__list {
|
|
9
|
+
display: grid;
|
|
10
|
+
min-width: 0;
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
gap: var(--s-space-control-gap);
|
|
14
|
+
grid-auto-columns: minmax(var(--s-wizard-step-min-inline-size), max-content);
|
|
15
|
+
grid-auto-flow: column;
|
|
16
|
+
list-style: none;
|
|
17
|
+
overflow-x: auto;
|
|
18
|
+
overscroll-behavior-inline: contain;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.s-wizard-steps[data-columns='2'] .s-wizard-steps__list,
|
|
22
|
+
.s-wizard-steps[data-columns='3'] .s-wizard-steps__list,
|
|
23
|
+
.s-wizard-steps[data-columns='4'] .s-wizard-steps__list {
|
|
24
|
+
overflow: visible;
|
|
25
|
+
grid-auto-flow: row;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.s-wizard-steps[data-columns='2'] .s-wizard-steps__list {
|
|
29
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.s-wizard-steps[data-columns='3'] .s-wizard-steps__list {
|
|
33
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.s-wizard-steps[data-columns='4'] .s-wizard-steps__list {
|
|
37
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.s-wizard-steps__item {
|
|
41
|
+
min-width: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.s-wizard-steps__marker {
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
width: var(--s-wizard-step-marker-size);
|
|
47
|
+
height: var(--s-wizard-step-marker-size);
|
|
48
|
+
flex: 0 0 auto;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
border: var(--s-wizard-step-marker-border-width) solid currentColor;
|
|
52
|
+
border-radius: var(--s-radius-pill);
|
|
53
|
+
background: color-mix(in srgb, currentColor 8%, transparent);
|
|
54
|
+
font-size: var(--s-wizard-step-marker-font-size);
|
|
55
|
+
font-weight: 700;
|
|
56
|
+
line-height: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.s-wizard-steps__item[data-state='current'] .s-wizard-steps__marker,
|
|
60
|
+
.s-wizard-steps__item[data-state='completed'] .s-wizard-steps__marker {
|
|
61
|
+
background: color-mix(in srgb, currentColor 16%, transparent);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.s-wizard-steps__title {
|
|
65
|
+
min-width: 0;
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
text-overflow: ellipsis;
|
|
68
|
+
white-space: nowrap;
|
|
69
|
+
}
|