@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,27 @@
1
+ /*
2
+ Стили для компонента SModal.
3
+ */
4
+ @reference "../../../styles/reference.css";
5
+
6
+ .s-modal-backdrop {
7
+ /*
8
+ Модальные окна должны перекрывать любые панели приложения:
9
+ мобильный чат, нижнюю навигацию, поповеры и плавающие окна.
10
+ */
11
+ @apply fixed inset-0;
12
+ background: var(--s-overlay-backdrop-surface);
13
+ -webkit-backdrop-filter: blur(var(--s-overlay-backdrop-blur));
14
+ backdrop-filter: blur(var(--s-overlay-backdrop-blur));
15
+ }
16
+
17
+ .s-modal-container {
18
+ @apply fixed inset-0 overflow-y-auto;
19
+ }
20
+
21
+ .s-modal-panel-container {
22
+ @apply flex min-h-full items-center justify-center p-4 text-center;
23
+ }
24
+
25
+ .s-modal-panel-container--fullscreen {
26
+ @apply items-stretch justify-stretch p-0 text-left;
27
+ }
@@ -0,0 +1,105 @@
1
+ <template>
2
+ <teleport to="body">
3
+ <transition name="s-modal-backdrop">
4
+ <div v-if="open" class="s-modal-backdrop" :style="backdropStyle" aria-hidden="true"></div>
5
+ </transition>
6
+
7
+ <transition name="s-modal-panel">
8
+ <div v-if="open" class="s-modal-container" :style="surfaceStyle">
9
+ <div class="s-modal-panel-container" :class="{ 's-modal-panel-container--fullscreen': isFullscreen }">
10
+ <SDialogSurface
11
+ v-bind="ownedAttrs.bindings()"
12
+ :presentation="dialogPresentation"
13
+ :title="title"
14
+ :accessible-name="ariaLabel"
15
+ :closable="closable"
16
+ :set-surface-element="setPanelElement"
17
+ @request-close="closeModal"
18
+ >
19
+ <template v-if="$slots.header" #header>
20
+ <slot name="header" />
21
+ </template>
22
+ <slot />
23
+ <template v-if="$slots.footer" #footer="{ close }">
24
+ <slot name="footer" :close="close" />
25
+ </template>
26
+ <template v-if="$slots['footer-start']" #footer-start="{ close }">
27
+ <slot name="footer-start" :close="close" />
28
+ </template>
29
+ <template v-if="$slots['footer-end']" #footer-end="{ close }">
30
+ <slot name="footer-end" :close="close" />
31
+ </template>
32
+ </SDialogSurface>
33
+ </div>
34
+ </div>
35
+ </transition>
36
+ </teleport>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ import { computed, toRef } from 'vue'
41
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs'
42
+ import { useDialogOverlay } from '../../../composables/useDialogOverlay'
43
+ import SDialogSurface from '../_internal/SDialogSurface.vue'
44
+ import type {
45
+ DialogSurfaceModalSize,
46
+ DialogSurfaceScrollBehavior,
47
+ DialogSurfaceTone,
48
+ ModalDialogSurfacePresentation,
49
+ } from '../_internal/dialogSurface'
50
+
51
+ defineOptions({ inheritAttrs: false })
52
+
53
+ export type ModalSize = DialogSurfaceModalSize
54
+ export type ModalSurface = DialogSurfaceTone
55
+ export type ModalScrollBehavior = DialogSurfaceScrollBehavior
56
+
57
+ export interface Props {
58
+ open: boolean
59
+ title?: string
60
+ closable?: boolean
61
+ size?: ModalSize
62
+ fullScreen?: boolean
63
+ layout?: 'desktop' | 'mobile-fullscreen'
64
+ surface?: ModalSurface
65
+ scrollBehavior?: ModalScrollBehavior
66
+ ariaLabel?: string
67
+ }
68
+
69
+ const props = withDefaults(defineProps<Props>(), {
70
+ title: undefined,
71
+ closable: true,
72
+ size: 'md',
73
+ fullScreen: false,
74
+ layout: 'desktop',
75
+ surface: 'default',
76
+ scrollBehavior: 'page',
77
+ ariaLabel: 'Модальное окно',
78
+ })
79
+
80
+ const emit = defineEmits<{
81
+ 'update:open': [value: boolean]
82
+ }>()
83
+
84
+ function closeModal() {
85
+ emit('update:open', false)
86
+ }
87
+
88
+ const isFullscreen = computed(() => props.fullScreen || props.layout === 'mobile-fullscreen')
89
+
90
+ const ownedAttrs = useOwnedAttrs({ component: 'SModal', owner: 'modal dialog surface' })
91
+ const dialogPresentation = computed<ModalDialogSurfacePresentation>(() => ({
92
+ kind: 'modal',
93
+ size: props.size,
94
+ fullscreen: isFullscreen.value,
95
+ surface: props.surface,
96
+ scrollBehavior: props.scrollBehavior,
97
+ }))
98
+ const { setPanelElement, backdropStyle, surfaceStyle } = useDialogOverlay({
99
+ open: toRef(props, 'open'),
100
+ closable: toRef(props, 'closable'),
101
+ requestClose: closeModal,
102
+ })
103
+ </script>
104
+
105
+ <style lang="postcss" src="./SModal.css" scoped></style>
@@ -0,0 +1,66 @@
1
+ @reference "../../../styles/reference.css";
2
+
3
+ .s-page-header {
4
+ @apply flex min-w-0 items-start justify-between gap-4 border border-surface-200 bg-surface-0 p-5 shadow-sm;
5
+ @apply dark:border-surface-700 dark:bg-surface-900;
6
+ border-radius: var(--radius-sm);
7
+ }
8
+
9
+ .s-page-header[data-appearance='plain'] {
10
+ @apply border-x-0 border-t-0 bg-transparent px-0 pt-0 shadow-none dark:bg-transparent;
11
+ border-radius: 0;
12
+ }
13
+
14
+ .s-page-header[data-size='compact'] {
15
+ @apply p-4;
16
+ }
17
+
18
+ .s-page-header[data-appearance='plain'][data-size='compact'] {
19
+ @apply px-0 pt-0;
20
+ }
21
+
22
+ .s-page-header__copy {
23
+ @apply min-w-0;
24
+ }
25
+
26
+ .s-page-header__eyebrow {
27
+ @apply text-xs font-medium uppercase text-surface-500 dark:text-surface-400;
28
+ }
29
+
30
+ .s-page-header__title {
31
+ @apply m-0 text-2xl font-semibold leading-tight text-surface-900 dark:text-surface-50;
32
+ }
33
+
34
+ .s-page-header[data-size='compact'] .s-page-header__title {
35
+ @apply text-xl;
36
+ }
37
+
38
+ .s-page-header__eyebrow + .s-page-header__title {
39
+ @apply mt-1;
40
+ }
41
+
42
+ .s-page-header__description {
43
+ @apply mt-2 max-w-4xl text-sm text-surface-500 dark:text-surface-400;
44
+ }
45
+
46
+ .s-page-header__actions {
47
+ @apply flex shrink-0 flex-wrap items-center justify-end gap-2;
48
+ }
49
+
50
+ .s-page-header[data-actions-layout='stacked'] {
51
+ @apply flex-col;
52
+ }
53
+
54
+ .s-page-header[data-actions-layout='stacked'] .s-page-header__actions {
55
+ @apply w-full justify-start;
56
+ }
57
+
58
+ @media (max-width: 639px) {
59
+ .s-page-header[data-actions-layout='responsive'] {
60
+ @apply flex-col;
61
+ }
62
+
63
+ .s-page-header[data-actions-layout='responsive'] .s-page-header__actions {
64
+ @apply w-full justify-start;
65
+ }
66
+ }
@@ -0,0 +1,45 @@
1
+ <script setup lang="ts">
2
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs'
3
+
4
+ defineOptions({ inheritAttrs: false })
5
+
6
+ withDefaults(defineProps<{
7
+ title: string
8
+ description?: string
9
+ eyebrow?: string
10
+ headingLevel?: 1 | 2 | 3
11
+ appearance?: 'plain' | 'panel'
12
+ size?: 'compact' | 'default'
13
+ actionsLayout?: 'responsive' | 'inline' | 'stacked'
14
+ }>(), {
15
+ description: undefined,
16
+ eyebrow: undefined,
17
+ headingLevel: 2,
18
+ appearance: 'panel',
19
+ size: 'default',
20
+ actionsLayout: 'responsive',
21
+ })
22
+
23
+ const ownedAttrs = useOwnedAttrs({ component: 'SPageHeader', owner: 'page header root' })
24
+ </script>
25
+
26
+ <template>
27
+ <header
28
+ v-bind="ownedAttrs.bindings()"
29
+ class="s-page-header"
30
+ :data-actions-layout="actionsLayout"
31
+ :data-appearance="appearance"
32
+ :data-size="size"
33
+ >
34
+ <div class="s-page-header__copy">
35
+ <div v-if="eyebrow" class="s-page-header__eyebrow">{{ eyebrow }}</div>
36
+ <component :is="`h${headingLevel}`" class="s-page-header__title">{{ title }}</component>
37
+ <p v-if="description" class="s-page-header__description">{{ description }}</p>
38
+ </div>
39
+ <div v-if="$slots.actions" class="s-page-header__actions">
40
+ <slot name="actions" />
41
+ </div>
42
+ </header>
43
+ </template>
44
+
45
+ <style lang="postcss" src="./SPageHeader.css" scoped></style>
@@ -0,0 +1,134 @@
1
+ @reference "../../../styles/reference.css";
2
+
3
+ .s-panel {
4
+ @apply relative flex flex-col rounded-lg border bg-surface-0 dark:bg-surface-800 border-surface-200 dark:border-surface-700;
5
+ @apply text-surface-700 dark:text-surface-300;
6
+ @apply min-h-0 min-w-0 w-full flex-none; /* предотвращаем горизонтальное распирание, панель заполняет доступную ширину; flex-none исключает сжатие панелей в flex-контейнерах */
7
+ }
8
+
9
+ .s-panel[data-grow='true'] {
10
+ flex: 1 1 0%;
11
+ height: 100%;
12
+ }
13
+
14
+ .s-panel--radius-none {
15
+ @apply rounded-none;
16
+ }
17
+
18
+ .s-panel--radius-md {
19
+ @apply rounded-md;
20
+ }
21
+
22
+ .s-panel--tone-transparent {
23
+ @apply bg-transparent dark:bg-transparent;
24
+ }
25
+
26
+ .s-panel--tone-subtle {
27
+ @apply bg-surface-0 dark:bg-surface-800/50;
28
+ }
29
+
30
+ .s-panel--border-none {
31
+ @apply border-none;
32
+ }
33
+
34
+ .s-panel--elevation-overlay {
35
+ box-shadow: var(--shadow-xl);
36
+ }
37
+
38
+ .s-panel[data-surface-overflow='clip'] {
39
+ overflow: clip;
40
+ }
41
+
42
+ .s-panel[data-layout='mobile'] {
43
+ @apply rounded-none border border-surface-100 dark:border-surface-700;
44
+ }
45
+
46
+ .s-panel-header {
47
+ @apply flex items-center justify-between p-3 border-b border-surface-200 dark:border-surface-700 shrink-0;
48
+ }
49
+
50
+ .s-panel-header-left {
51
+ @apply flex items-center min-w-0;
52
+ gap: var(--s-space-control-gap);
53
+ }
54
+
55
+ .s-panel-icon {
56
+ @apply h-5 w-5 text-surface-500 dark:text-surface-400;
57
+ }
58
+
59
+ .s-panel-title {
60
+ @apply text-base font-semibold text-surface-800 dark:text-surface-200 truncate;
61
+ }
62
+
63
+ .s-panel-heading {
64
+ @apply min-w-0;
65
+ }
66
+
67
+ .s-panel-description {
68
+ @apply mt-0.5 whitespace-normal text-sm text-surface-500 dark:text-surface-400;
69
+ overflow-wrap: anywhere;
70
+ }
71
+
72
+ .s-panel-header-right {
73
+ @apply flex items-center min-w-0 shrink-0;
74
+ gap: var(--s-space-content-gap);
75
+ }
76
+
77
+ .s-panel-content {
78
+ /* Базово без внутренних скроллов; прокрутка включается только для grow-панелей */
79
+ @apply min-h-0 min-w-0 w-full; /* безопасная ширина для любых сеток/инпутов внутри */
80
+ }
81
+
82
+ .s-panel-content[data-content-layout='stack'] {
83
+ @apply flex flex-col;
84
+ }
85
+
86
+ .s-panel-content[data-content-layout='stack'][data-content-gap='control'] {
87
+ gap: var(--s-space-control-gap);
88
+ }
89
+
90
+ .s-panel-content[data-content-layout='stack'][data-content-gap='content'] {
91
+ gap: var(--s-space-content-gap);
92
+ }
93
+
94
+ .s-panel-content[data-content-layout='stack'][data-content-gap='section'] {
95
+ gap: var(--s-space-section-gap);
96
+ }
97
+
98
+ .s-panel-content[data-content-layout='stack'][data-content-gap='panel'] {
99
+ gap: var(--s-space-panel-gap);
100
+ }
101
+
102
+ .s-panel-scroll-end-spacer {
103
+ height: 1rem;
104
+ flex: 0 0 1rem;
105
+ pointer-events: none;
106
+ }
107
+
108
+ .s-panel-content[data-grow='true'] > * {
109
+ flex-shrink: 0;
110
+ }
111
+
112
+ .s-panel-content--scroll-body {
113
+ @apply overflow-visible; /* прокручивает родитель body/контейнер */
114
+ }
115
+
116
+ .s-panel-content--scroll-self {
117
+ @apply overflow-y-auto overflow-x-hidden;
118
+ }
119
+
120
+ .s-panel-content--scroll-child {
121
+ overflow: hidden;
122
+ }
123
+
124
+ .s-panel-footer {
125
+ @apply flex min-h-8 min-w-0 flex-wrap items-center border-t border-surface-200 px-3 py-1.5 text-xs text-surface-500;
126
+ @apply dark:border-surface-700 dark:text-surface-400;
127
+ gap: var(--s-space-control-gap);
128
+ }
129
+
130
+ /* Вложенные компоненты сохраняют собственную высоту; grow управляет прокруткой SPanel. */
131
+ .s-panel-loading {
132
+ @apply flex w-full max-w-md flex-col text-center;
133
+ gap: var(--s-space-content-gap);
134
+ }
@@ -0,0 +1,169 @@
1
+ <template>
2
+ <div
3
+ v-bind="ownedAttrs.bindings()"
4
+ class="s-panel"
5
+ :class="[
6
+ scrollBehavior && `s-panel--scroll-${scrollBehavior}`,
7
+ surfaceRadius === 'none' && 's-panel--radius-none',
8
+ surfaceRadius === 'md' && 's-panel--radius-md',
9
+ surfaceTone === 'transparent' && 's-panel--tone-transparent',
10
+ surfaceTone === 'subtle' && 's-panel--tone-subtle',
11
+ surfaceBorder === 'none' && 's-panel--border-none',
12
+ surfaceElevation === 'overlay' && 's-panel--elevation-overlay',
13
+ ]"
14
+ :data-grow="grow || undefined"
15
+ :data-layout="layout"
16
+ :data-surface-overflow="surfaceOverflow"
17
+ >
18
+ <div
19
+ v-if="title || $slots.actions || $slots.header"
20
+ class="s-panel-header"
21
+ >
22
+ <div class="s-panel-header-left">
23
+ <slot name="header">
24
+ <component :is="icon" v-if="icon" class="s-panel-icon" />
25
+ <div v-if="title" class="s-panel-heading">
26
+ <component :is="`h${headingLevel}`" :id="validatedTitleId" class="s-panel-title">{{ title }}</component>
27
+ <p v-if="description" class="s-panel-description">{{ description }}</p>
28
+ </div>
29
+ </slot>
30
+ </div>
31
+ <div class="s-panel-header-right">
32
+ <slot name="actions" />
33
+ </div>
34
+ </div>
35
+ <div
36
+ class="s-panel-content"
37
+ :id="validatedContentId"
38
+ :class="{
39
+ 'p-4': !noContentPadding,
40
+ 'flex-grow min-h-0 min-w-0 overflow-y-auto overflow-x-hidden': grow,
41
+ 's-panel-content--scroll-body': scrollBehavior === 'body',
42
+ 's-panel-content--scroll-self': scrollBehavior === 'self',
43
+ 's-panel-content--scroll-child': scrollBehavior === 'child',
44
+ }"
45
+ :data-content-layout="contentLayout"
46
+ :data-content-gap="contentGap"
47
+ :data-grow="grow || undefined"
48
+ >
49
+ <SAsyncState :state="state">
50
+ <template v-if="state.status === 'error' && state.endpoint" #error>
51
+ <SMessage severity="danger" title="Ошибка ответа сервера">
52
+ <div class="space-y-1">
53
+ <div>{{ state.message }}</div>
54
+ <div class="text-xs text-danger-700 dark:text-danger-300">
55
+ Запрос: {{ state.endpoint }}
56
+ </div>
57
+ </div>
58
+ </SMessage>
59
+ </template>
60
+ <template #loading="{ message }">
61
+ <div class="s-panel-loading">
62
+ <SProgressBar :accessibility="{ mode: 'label', label: message }" mode="indeterminate" />
63
+ <span>{{ message }}</span>
64
+ </div>
65
+ </template>
66
+ <slot />
67
+ </SAsyncState>
68
+ <div v-if="grow" class="s-panel-scroll-end-spacer" aria-hidden="true"></div>
69
+ </div>
70
+ <div v-if="$slots.footer" :id="validatedFooterId" class="s-panel-footer">
71
+ <slot name="footer" />
72
+ </div>
73
+ </div>
74
+ </template>
75
+
76
+ <script setup lang="ts">
77
+ import { computed, useSlots, watchEffect, type FunctionalComponent, type HTMLAttributes, type VNodeProps } from 'vue'
78
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs'
79
+ import { validateDomId } from '../../../internal/runtimeContract'
80
+ import SAsyncState, { type SAsyncStateContract } from '../feedback/SAsyncState.vue'
81
+ import SMessage from '../data-display/SMessage.vue'
82
+ import SProgressBar from '../data-display/SProgressBar.vue'
83
+
84
+ defineOptions({ inheritAttrs: false })
85
+
86
+ export interface Props {
87
+ title?: string
88
+ /** DOM id встроенного heading для связи с composed content. / DOM id of the built-in heading for composed-content labelling. */
89
+ titleId?: string
90
+ /** DOM id content viewport для composed navigation и testing. / DOM id of the content viewport for composed navigation and testing. */
91
+ contentId?: string
92
+ description?: string
93
+ /** Уровень заголовка в document outline. / Heading level in the document outline. */
94
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6
95
+ icon?: FunctionalComponent<HTMLAttributes & VNodeProps>
96
+ /** Единственное состояние контента; omission означает ready. / The sole content-state contract; omission means ready. */
97
+ state?: SAsyncStateContract
98
+ noContentPadding?: boolean
99
+ /** Компоновка slot content без скрытого воздействия на child margins. / Slot-content layout without hidden child-margin styling. */
100
+ contentLayout?: 'block' | 'stack'
101
+ /** Семантический интервал layout; none не добавляет неявный spacing. / Semantic layout gap; none adds no implicit spacing. */
102
+ contentGap?: 'none' | 'control' | 'content' | 'section' | 'panel'
103
+ grow?: boolean
104
+ layout?: 'desktop' | 'mobile'
105
+ /** child делегирует scrolling вложенному владельцу viewport. / child delegates scrolling to the nested viewport owner. */
106
+ scrollBehavior?: 'auto' | 'body' | 'self' | 'child'
107
+ /** Семантический радиус поверхности; значения не принимают raw CSS lengths. / Semantic surface radius; values never accept raw CSS lengths. */
108
+ surfaceRadius?: 'default' | 'md' | 'none'
109
+ /** Семантический тон поверхности. / Semantic surface tone. */
110
+ surfaceTone?: 'default' | 'subtle' | 'transparent'
111
+ surfaceBorder?: 'default' | 'none'
112
+ /** Семантическая высота тени surface. / Semantic surface shadow elevation. */
113
+ surfaceElevation?: 'flat' | 'overlay'
114
+ /** Управляет clipping корневой surface без consumer class overrides. / Controls root-surface clipping without consumer class overrides. */
115
+ surfaceOverflow?: 'visible' | 'clip'
116
+ /** DOM id общего footer/status region. / DOM id of the shared footer/status region. */
117
+ footerId?: string
118
+ }
119
+
120
+ const props = withDefaults(defineProps<Props>(), {
121
+ title: undefined,
122
+ titleId: undefined,
123
+ contentId: undefined,
124
+ description: undefined,
125
+ headingLevel: 3,
126
+ icon: undefined,
127
+ state: () => ({ status: 'ready' }),
128
+ noContentPadding: false,
129
+ contentLayout: 'stack',
130
+ contentGap: 'none',
131
+ grow: false,
132
+ layout: 'desktop',
133
+ scrollBehavior: 'auto',
134
+ surfaceRadius: 'default',
135
+ surfaceTone: 'default',
136
+ surfaceBorder: 'default',
137
+ surfaceElevation: 'flat',
138
+ surfaceOverflow: 'visible',
139
+ footerId: undefined,
140
+ })
141
+
142
+ const slots = useSlots()
143
+ const ownedAttrs = useOwnedAttrs({ component: 'SPanel', owner: 'panel surface root' })
144
+ const validatedTitleId = computed(() => {
145
+ if (props.titleId === undefined) return undefined
146
+ if (!props.title) {
147
+ throw new TypeError('SPanel: titleId требует title. / SPanel: titleId requires title.')
148
+ }
149
+ return validateDomId('SPanel', 'titleId', props.titleId)
150
+ })
151
+ const validatedContentId = computed(() => props.contentId === undefined
152
+ ? undefined
153
+ : validateDomId('SPanel', 'contentId', props.contentId))
154
+ const validatedFooterId = computed(() => {
155
+ if (props.footerId === undefined) return undefined
156
+ if (!slots.footer) {
157
+ throw new TypeError('SPanel: footerId требует footer slot. / SPanel: footerId requires the footer slot.')
158
+ }
159
+ return validateDomId('SPanel', 'footerId', props.footerId)
160
+ })
161
+
162
+ watchEffect(() => {
163
+ void validatedTitleId.value
164
+ void validatedContentId.value
165
+ void validatedFooterId.value
166
+ })
167
+ </script>
168
+
169
+ <style lang="postcss" src="./SPanel.css" scoped></style>
@@ -0,0 +1,35 @@
1
+ @reference "../../../styles/reference.css";
2
+
3
+ .s-popover-wrapper {
4
+ @apply relative;
5
+ }
6
+
7
+ .s-popover-panel {
8
+ display: inline-block;
9
+ }
10
+
11
+ .s-popover-content {
12
+ @apply rounded-lg text-left;
13
+ background: var(--s-floating-surface);
14
+ color: var(--s-floating-foreground);
15
+ box-shadow: 0 0 0 1px var(--s-floating-outline), var(--s-elevation-floating);
16
+ display: inline-block;
17
+ }
18
+
19
+ .s-popover-panel[data-width='sm'] {
20
+ width: min(20rem, calc(100vw - 1rem));
21
+ }
22
+
23
+ .s-popover-panel[data-width='md'] {
24
+ width: min(28rem, calc(100vw - 1rem));
25
+ }
26
+
27
+ .s-popover-panel[data-width='lg'] {
28
+ width: min(36rem, calc(100vw - 1rem));
29
+ }
30
+
31
+ .s-popover-panel[data-surface='transparent'] .s-popover-content {
32
+ @apply bg-transparent shadow-none dark:bg-transparent;
33
+ box-shadow: none;
34
+ max-width: 90vw;
35
+ }