@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.
Files changed (251) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/docs/accessibility.md +27 -0
  4. package/docs/getting-started.md +80 -0
  5. package/docs/licensing.md +30 -0
  6. package/docs/public-api.md +39 -0
  7. package/docs/security.md +21 -0
  8. package/docs/theming.md +35 -0
  9. package/docs/versioning.md +23 -0
  10. package/index.ts +3 -0
  11. package/package.json +592 -0
  12. package/src/components/icons/_internal/generatedSputnigUiIconMarkup.ts +971 -0
  13. package/src/components/icons/sputnigUiIcons.ts +530 -0
  14. package/src/components/layout/SAppShell.vue +232 -0
  15. package/src/components/layout/SDockRegion.vue +200 -0
  16. package/src/components/layout/SResizeHandle.vue +251 -0
  17. package/src/components/layout/SSideMenuButton.vue +104 -0
  18. package/src/components/layout/SSideMenuRail.vue +47 -0
  19. package/src/components/layout/SStack.css +25 -0
  20. package/src/components/layout/SStack.vue +57 -0
  21. package/src/components/layout/SStatusBarShell.css +15 -0
  22. package/src/components/layout/SStatusBarShell.vue +26 -0
  23. package/src/components/layout/SWorkbenchLayout.vue +53 -0
  24. package/src/components/layout/SWorkbenchPanelShell.css +161 -0
  25. package/src/components/layout/SWorkbenchPanelShell.vue +286 -0
  26. package/src/components/layout/_internal/SAppBrand.css +45 -0
  27. package/src/components/layout/_internal/SAppBrand.vue +37 -0
  28. package/src/components/layout/_internal/SAppBrandContent.vue +30 -0
  29. package/src/components/layout/types.ts +28 -0
  30. package/src/components/shared/_internal/SDialogSurface.css +127 -0
  31. package/src/components/shared/_internal/SDialogSurface.vue +117 -0
  32. package/src/components/shared/_internal/SDisclosureSummary.css +92 -0
  33. package/src/components/shared/_internal/SDisclosureSummary.vue +121 -0
  34. package/src/components/shared/_internal/SListboxGroup.css +18 -0
  35. package/src/components/shared/_internal/SListboxGroup.vue +24 -0
  36. package/src/components/shared/_internal/SMenuItem.css +112 -0
  37. package/src/components/shared/_internal/SMenuItem.vue +213 -0
  38. package/src/components/shared/_internal/SMenuLayer.css +21 -0
  39. package/src/components/shared/_internal/SMenuLayer.vue +48 -0
  40. package/src/components/shared/_internal/SMenuSeparator.css +5 -0
  41. package/src/components/shared/_internal/SMenuSeparator.vue +13 -0
  42. package/src/components/shared/_internal/SToastItem.css +65 -0
  43. package/src/components/shared/_internal/SToastItem.vue +58 -0
  44. package/src/components/shared/_internal/STreeNode.vue +761 -0
  45. package/src/components/shared/_internal/dialogSurface.ts +105 -0
  46. package/src/components/shared/_internal/focusTrap.ts +40 -0
  47. package/src/components/shared/_internal/listboxContext.ts +16 -0
  48. package/src/components/shared/_internal/markerContract.ts +10 -0
  49. package/src/components/shared/_internal/menuContext.ts +23 -0
  50. package/src/components/shared/_internal/selectionContract.ts +239 -0
  51. package/src/components/shared/_internal/sidebarGroupContext.ts +31 -0
  52. package/src/components/shared/_internal/tabsContext.ts +57 -0
  53. package/src/components/shared/_internal/treeRuntime.ts +203 -0
  54. package/src/components/shared/_internal/useSelectionRoving.ts +162 -0
  55. package/src/components/shared/codeLanguages.ts +168 -0
  56. package/src/components/shared/complex/SKanbanCard.css +8 -0
  57. package/src/components/shared/complex/SKanbanCard.vue +76 -0
  58. package/src/components/shared/complex/SKanbanLane.vue +82 -0
  59. package/src/components/shared/complex/STree.css +24 -0
  60. package/src/components/shared/complex/STree.vue +288 -0
  61. package/src/components/shared/complex/treeAdapter.ts +210 -0
  62. package/src/components/shared/complex/types.ts +102 -0
  63. package/src/components/shared/containers/SCloudChrome.css +103 -0
  64. package/src/components/shared/containers/SCloudChrome.vue +87 -0
  65. package/src/components/shared/containers/SCloudChromeEmpty.css +10 -0
  66. package/src/components/shared/containers/SCloudChromeEmpty.vue +18 -0
  67. package/src/components/shared/containers/SCloudChromeRow.css +78 -0
  68. package/src/components/shared/containers/SCloudChromeRow.vue +108 -0
  69. package/src/components/shared/containers/SCollapsiblePanel.css +57 -0
  70. package/src/components/shared/containers/SCollapsiblePanel.vue +360 -0
  71. package/src/components/shared/containers/SDrawer.css +60 -0
  72. package/src/components/shared/containers/SDrawer.vue +86 -0
  73. package/src/components/shared/containers/SFormGrid.css +26 -0
  74. package/src/components/shared/containers/SFormGrid.vue +46 -0
  75. package/src/components/shared/containers/SFormGridItem.css +18 -0
  76. package/src/components/shared/containers/SFormGridItem.vue +29 -0
  77. package/src/components/shared/containers/SLeftSidebar.vue +74 -0
  78. package/src/components/shared/containers/SModal.css +27 -0
  79. package/src/components/shared/containers/SModal.vue +105 -0
  80. package/src/components/shared/containers/SPageHeader.css +66 -0
  81. package/src/components/shared/containers/SPageHeader.vue +45 -0
  82. package/src/components/shared/containers/SPanel.css +134 -0
  83. package/src/components/shared/containers/SPanel.vue +169 -0
  84. package/src/components/shared/containers/SPopover.css +35 -0
  85. package/src/components/shared/containers/SPopover.vue +551 -0
  86. package/src/components/shared/containers/SSidebarGroup.css +12 -0
  87. package/src/components/shared/containers/SSidebarGroup.vue +443 -0
  88. package/src/components/shared/containers/SSidebarSection.css +41 -0
  89. package/src/components/shared/containers/SSidebarSection.vue +268 -0
  90. package/src/components/shared/containers/disclosureSummary.ts +48 -0
  91. package/src/components/shared/containers/popover.ts +72 -0
  92. package/src/components/shared/containers/sidebar.ts +206 -0
  93. package/src/components/shared/controls/SButton.css +274 -0
  94. package/src/components/shared/controls/SButton.vue +211 -0
  95. package/src/components/shared/controls/SCheckbox.css +65 -0
  96. package/src/components/shared/controls/SCheckbox.vue +130 -0
  97. package/src/components/shared/controls/SCheckboxGroup.vue +134 -0
  98. package/src/components/shared/controls/SChoiceCards.css +29 -0
  99. package/src/components/shared/controls/SChoiceCards.vue +145 -0
  100. package/src/components/shared/controls/SColorSelect.vue +146 -0
  101. package/src/components/shared/controls/SComboboxTrigger.css +35 -0
  102. package/src/components/shared/controls/SComboboxTrigger.vue +122 -0
  103. package/src/components/shared/controls/SContextMenu.css +11 -0
  104. package/src/components/shared/controls/SContextMenu.vue +186 -0
  105. package/src/components/shared/controls/SContextToggleButton.vue +64 -0
  106. package/src/components/shared/controls/SDragHandle.vue +100 -0
  107. package/src/components/shared/controls/SDropdownMenu.css +5 -0
  108. package/src/components/shared/controls/SDropdownMenu.vue +188 -0
  109. package/src/components/shared/controls/SField.css +79 -0
  110. package/src/components/shared/controls/SField.vue +89 -0
  111. package/src/components/shared/controls/SFieldGroup.css +58 -0
  112. package/src/components/shared/controls/SFieldGroup.vue +117 -0
  113. package/src/components/shared/controls/SFieldLabel.css +12 -0
  114. package/src/components/shared/controls/SFieldLabel.vue +51 -0
  115. package/src/components/shared/controls/SInlineTokenEditor.css +61 -0
  116. package/src/components/shared/controls/SInlineTokenEditor.vue +1081 -0
  117. package/src/components/shared/controls/SInputText.css +66 -0
  118. package/src/components/shared/controls/SInputText.vue +321 -0
  119. package/src/components/shared/controls/SInteractiveSurface.css +72 -0
  120. package/src/components/shared/controls/SInteractiveSurface.vue +326 -0
  121. package/src/components/shared/controls/SLink.css +114 -0
  122. package/src/components/shared/controls/SLink.ts +85 -0
  123. package/src/components/shared/controls/SLink.vue +114 -0
  124. package/src/components/shared/controls/SListbox.css +31 -0
  125. package/src/components/shared/controls/SListbox.vue +388 -0
  126. package/src/components/shared/controls/SListboxOption.css +63 -0
  127. package/src/components/shared/controls/SListboxOption.vue +85 -0
  128. package/src/components/shared/controls/SMarker.css +19 -0
  129. package/src/components/shared/controls/SMarker.vue +33 -0
  130. package/src/components/shared/controls/SMenuSurface.css +20 -0
  131. package/src/components/shared/controls/SMenuSurface.vue +333 -0
  132. package/src/components/shared/controls/SSegmentedControl.css +38 -0
  133. package/src/components/shared/controls/SSegmentedControl.vue +134 -0
  134. package/src/components/shared/controls/SSelect.css +12 -0
  135. package/src/components/shared/controls/SSelect.vue +338 -0
  136. package/src/components/shared/controls/STextarea.css +31 -0
  137. package/src/components/shared/controls/STextarea.vue +316 -0
  138. package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +224 -0
  139. package/src/components/shared/controls/menu.ts +178 -0
  140. package/src/components/shared/data-display/SActionCard.css +6 -0
  141. package/src/components/shared/data-display/SActionCard.vue +49 -0
  142. package/src/components/shared/data-display/SActionList.css +15 -0
  143. package/src/components/shared/data-display/SActionList.vue +34 -0
  144. package/src/components/shared/data-display/SActionListItem.css +114 -0
  145. package/src/components/shared/data-display/SActionListItem.vue +230 -0
  146. package/src/components/shared/data-display/SBadge.css +73 -0
  147. package/src/components/shared/data-display/SBadge.vue +52 -0
  148. package/src/components/shared/data-display/SCodeBlock.css +56 -0
  149. package/src/components/shared/data-display/SCodeBlock.vue +188 -0
  150. package/src/components/shared/data-display/SCodeEditor.css +178 -0
  151. package/src/components/shared/data-display/SCodeEditor.vue +274 -0
  152. package/src/components/shared/data-display/SCodeSearchPanel.vue +327 -0
  153. package/src/components/shared/data-display/SCopyField.vue +77 -0
  154. package/src/components/shared/data-display/SDocBlock.css +136 -0
  155. package/src/components/shared/data-display/SDocBlock.vue +358 -0
  156. package/src/components/shared/data-display/SEmptyState.css +42 -0
  157. package/src/components/shared/data-display/SEmptyState.vue +79 -0
  158. package/src/components/shared/data-display/SExpandableText.vue +314 -0
  159. package/src/components/shared/data-display/SJsonTree.css +6 -0
  160. package/src/components/shared/data-display/SJsonTree.vue +111 -0
  161. package/src/components/shared/data-display/SKeyValueGrid.css +37 -0
  162. package/src/components/shared/data-display/SKeyValueGrid.vue +277 -0
  163. package/src/components/shared/data-display/SLinkedSystemsList.vue +270 -0
  164. package/src/components/shared/data-display/SMessage.css +88 -0
  165. package/src/components/shared/data-display/SMessage.vue +152 -0
  166. package/src/components/shared/data-display/SMetricCard.css +32 -0
  167. package/src/components/shared/data-display/SMetricCard.vue +117 -0
  168. package/src/components/shared/data-display/SProgressBar.css +84 -0
  169. package/src/components/shared/data-display/SProgressBar.vue +114 -0
  170. package/src/components/shared/data-display/SProgressIndicator.css +59 -0
  171. package/src/components/shared/data-display/SProgressIndicator.vue +104 -0
  172. package/src/components/shared/data-display/SSectionHeader.css +84 -0
  173. package/src/components/shared/data-display/SSectionHeader.vue +58 -0
  174. package/src/components/shared/data-display/SStatus.css +90 -0
  175. package/src/components/shared/data-display/SStatus.vue +79 -0
  176. package/src/components/shared/data-display/STable.css +83 -0
  177. package/src/components/shared/data-display/STable.vue +339 -0
  178. package/src/components/shared/data-display/SToastContainer.css +53 -0
  179. package/src/components/shared/data-display/SToastContainer.vue +31 -0
  180. package/src/components/shared/data-display/STooltip.css +114 -0
  181. package/src/components/shared/data-display/STooltip.vue +550 -0
  182. package/src/components/shared/data-display/STooltipAnchor.css +21 -0
  183. package/src/components/shared/data-display/STooltipAnchor.vue +119 -0
  184. package/src/components/shared/data-display/SVirtualList.css +24 -0
  185. package/src/components/shared/data-display/SVirtualList.ts +19 -0
  186. package/src/components/shared/data-display/SVirtualList.vue +361 -0
  187. package/src/components/shared/data-display/json.ts +136 -0
  188. package/src/components/shared/data-display/table.ts +76 -0
  189. package/src/components/shared/database/SDataGrid.css +3 -0
  190. package/src/components/shared/database/SDataGrid.vue +464 -0
  191. package/src/components/shared/database/SSqlEditor.vue +363 -0
  192. package/src/components/shared/database/dataGrid.ts +61 -0
  193. package/src/components/shared/feedback/SAsyncState.css +26 -0
  194. package/src/components/shared/feedback/SAsyncState.vue +190 -0
  195. package/src/components/shared/graph/SGraphViewport.ts +1 -0
  196. package/src/components/shared/graph/SGraphViewport.vue +858 -0
  197. package/src/components/shared/graph/_internal/graphViewportContract.ts +362 -0
  198. package/src/components/shared/markdown.ts +169 -0
  199. package/src/components/shared/navigation/SBottomNav.vue +692 -0
  200. package/src/components/shared/navigation/SBreadcrumbs.css +26 -0
  201. package/src/components/shared/navigation/SBreadcrumbs.vue +155 -0
  202. package/src/components/shared/navigation/SCatalogNavigator.css +100 -0
  203. package/src/components/shared/navigation/SCatalogNavigator.vue +308 -0
  204. package/src/components/shared/navigation/STab.vue +363 -0
  205. package/src/components/shared/navigation/STabList.vue +442 -0
  206. package/src/components/shared/navigation/STabPanel.vue +165 -0
  207. package/src/components/shared/navigation/STabPanels.vue +33 -0
  208. package/src/components/shared/navigation/STabs.vue +617 -0
  209. package/src/components/shared/navigation/SWizardSteps.css +69 -0
  210. package/src/components/shared/navigation/SWizardSteps.vue +171 -0
  211. package/src/components/shared/navigation/_internal/SCatalogNavigatorGroup.vue +63 -0
  212. package/src/components/shared/persona/SPersonaProjectPicker.css +36 -0
  213. package/src/components/shared/persona/SPersonaProjectPicker.vue +361 -0
  214. package/src/components/shared/persona/SPersonaRunWorkbench.css +109 -0
  215. package/src/components/shared/persona/SPersonaRunWorkbench.vue +549 -0
  216. package/src/components/shared/persona/_internal/SPersonaRunConsole.css +40 -0
  217. package/src/components/shared/persona/_internal/SPersonaRunConsole.vue +298 -0
  218. package/src/components/shared/persona/_internal/inventory.ts +110 -0
  219. package/src/components/shared/persona/_internal/labels.ts +79 -0
  220. package/src/components/shared/persona/presentation.ts +371 -0
  221. package/src/components/shared/persona/types.ts +256 -0
  222. package/src/composables/uiPreferencesReset.ts +52 -0
  223. package/src/composables/useClipboard.ts +37 -0
  224. package/src/composables/useDialogOverlay.ts +85 -0
  225. package/src/composables/useDisclosure.ts +40 -0
  226. package/src/composables/useFloatingPosition.ts +245 -0
  227. package/src/composables/useNotifier.ts +26 -0
  228. package/src/composables/useSidebarPanelState.ts +610 -0
  229. package/src/internal/codeEditorContract.ts +110 -0
  230. package/src/internal/codeEditorExtensions.ts +27 -0
  231. package/src/internal/feedbackPresentation.ts +136 -0
  232. package/src/internal/fieldSurface.ts +59 -0
  233. package/src/internal/focusNavigation.ts +69 -0
  234. package/src/internal/inlineTokenEditorContract.ts +136 -0
  235. package/src/internal/interactiveElement.ts +33 -0
  236. package/src/internal/layerStack.ts +202 -0
  237. package/src/internal/linkTarget.ts +256 -0
  238. package/src/internal/ownedAttrs.ts +464 -0
  239. package/src/internal/passiveContentContract.ts +129 -0
  240. package/src/internal/pointerInteractionLease.ts +81 -0
  241. package/src/internal/runtimeContract.ts +194 -0
  242. package/src/internal/runtimeTheme.ts +159 -0
  243. package/src/internal/semanticSizing.ts +60 -0
  244. package/src/internal/useMenuLayer.ts +129 -0
  245. package/src/stores/useNotifierStore.ts +69 -0
  246. package/src/styles/reference.css +4 -0
  247. package/src/styles/style.css +151 -0
  248. package/src/styles/tokens.css +836 -0
  249. package/src/theme.ts +449 -0
  250. package/src/types/highlight-js-lib.d.ts +76 -0
  251. 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
+ }