@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,230 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import type { Component, VNodeChild } from 'vue'
4
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs'
5
+ import { validateBoundedInteger, validateNonEmptyString } from '../../../internal/runtimeContract'
6
+
7
+ import SButton from '../controls/SButton.vue'
8
+ import SActionCard from './SActionCard.vue'
9
+ import SBadge from './SBadge.vue'
10
+ import type { BadgeSeverity } from './SBadge.vue'
11
+ import type { Props as ButtonProps } from '../controls/SButton.vue'
12
+ import type { InteractiveSurfaceContract, InteractiveSurfaceDensity, InteractiveSurfacePadding, InteractiveSurfaceRadius, InteractiveSurfaceTone } from '../controls/SInteractiveSurface.vue'
13
+
14
+ defineOptions({ inheritAttrs: false })
15
+
16
+ export interface ActionListItemBadge {
17
+ readonly id: string
18
+ readonly label: string
19
+ readonly severity: BadgeSeverity
20
+ }
21
+
22
+ export interface ActionListItemActionsSlotProps {
23
+ readonly disabled: boolean
24
+ readonly emitAction: (event: MouseEvent) => void
25
+ }
26
+
27
+ const props = withDefaults(defineProps<{
28
+ title: string
29
+ titleTooltip?: string
30
+ description?: string | null
31
+ detail?: string | null
32
+ icon?: Component
33
+ surface?: InteractiveSurfaceContract
34
+ indentLevel?: number
35
+ selected?: boolean
36
+ tone?: InteractiveSurfaceTone
37
+ density?: InteractiveSurfaceDensity
38
+ surfacePadding?: InteractiveSurfacePadding
39
+ surfaceRadius?: InteractiveSurfaceRadius
40
+ actionLabel?: string
41
+ actionIcon?: Component
42
+ actionSeverity?: ButtonProps['severity']
43
+ actionDisabled?: boolean
44
+ statusLabel?: string | null
45
+ statusSeverity?: BadgeSeverity
46
+ badges?: readonly ActionListItemBadge[]
47
+ trailingIcon?: Component
48
+ trailingLayout?: 'responsive' | 'inline' | 'stacked'
49
+ heightMode?: 'content' | 'fill'
50
+ titlePresentation?: 'text' | 'code'
51
+ titleWeight?: 'regular' | 'semibold'
52
+ detailPresentation?: 'text' | 'code'
53
+ detailOverflow?: 'clamp-2' | 'wrap'
54
+ }>(), {
55
+ description: undefined,
56
+ titleTooltip: undefined,
57
+ detail: undefined,
58
+ icon: undefined,
59
+ surface: undefined,
60
+ indentLevel: 0,
61
+ selected: false,
62
+ tone: 'default',
63
+ density: 'comfortable',
64
+ surfacePadding: 'none',
65
+ surfaceRadius: 'md',
66
+ actionLabel: undefined,
67
+ actionIcon: undefined,
68
+ actionSeverity: 'secondary',
69
+ actionDisabled: false,
70
+ statusLabel: undefined,
71
+ statusSeverity: 'secondary',
72
+ badges: () => [],
73
+ trailingIcon: undefined,
74
+ trailingLayout: 'responsive',
75
+ heightMode: 'content',
76
+ titlePresentation: 'text',
77
+ titleWeight: 'semibold',
78
+ detailPresentation: 'text',
79
+ detailOverflow: 'clamp-2',
80
+ })
81
+
82
+ const emit = defineEmits<{
83
+ activate: [event: MouseEvent]
84
+ action: [event: MouseEvent]
85
+ }>()
86
+
87
+ const slots = defineSlots<{
88
+ title?: () => VNodeChild
89
+ description?: () => VNodeChild
90
+ detail?: () => VNodeChild
91
+ status?: () => VNodeChild
92
+ actions?: (props: ActionListItemActionsSlotProps) => VNodeChild
93
+ }>()
94
+
95
+ const validatedIndentLevel = computed(() => validateBoundedInteger(
96
+ 'SActionListItem',
97
+ 'indentLevel',
98
+ props.indentLevel,
99
+ { minimum: 0, maximum: 8 },
100
+ ))
101
+ const itemStyle = computed(() => ({
102
+ '--s-action-list-item-indent': `${validatedIndentLevel.value * 0.875}rem`,
103
+ }))
104
+ const ownedAttrs = useOwnedAttrs({ component: 'SActionListItem', owner: 'action list item root' })
105
+ const rootBindings = computed(() => ownedAttrs.bindings())
106
+ const semanticRole = computed(() => {
107
+ const declaredRole = rootBindings.value.role
108
+ return typeof declaredRole === 'string' ? declaredRole : 'listitem'
109
+ })
110
+
111
+ const validatedBadges = computed<readonly ActionListItemBadge[]>(() => {
112
+ if (!Array.isArray(props.badges)) {
113
+ throw new TypeError(
114
+ 'SActionListItem: badges должен быть массивом. '
115
+ + '/ SActionListItem: badges must be an array.',
116
+ )
117
+ }
118
+
119
+ const identities = new Set<string>()
120
+ for (const [index, badge] of props.badges.entries()) {
121
+ const candidate: unknown = badge
122
+ const badgeId = validateNonEmptyString(
123
+ 'SActionListItem',
124
+ `badges[${index}].id`,
125
+ typeof candidate === 'object' && candidate !== null && !Array.isArray(candidate)
126
+ ? (candidate as Record<string, unknown>).id
127
+ : undefined,
128
+ )
129
+ if (identities.has(badgeId)) {
130
+ throw new TypeError(
131
+ `SActionListItem: badges должен содержать уникальные id; дублируется ${JSON.stringify(badgeId)}. `
132
+ + `/ SActionListItem: badges must contain unique ids; duplicate ${JSON.stringify(badgeId)}.`,
133
+ )
134
+ }
135
+ identities.add(badgeId)
136
+ }
137
+ return props.badges
138
+ })
139
+
140
+ const hasTrailing = computed(() => Boolean(
141
+ props.actionLabel
142
+ || props.statusLabel
143
+ || validatedBadges.value.length > 0
144
+ || props.trailingIcon
145
+ || slots.status
146
+ || slots.actions,
147
+ ))
148
+
149
+ const resolvedSurface = computed<InteractiveSurfaceContract>(() => props.surface ?? {
150
+ mode: 'action',
151
+ accessibleLabel: props.title,
152
+ })
153
+ const resolvedMode = computed(() => resolvedSurface.value.mode)
154
+ const surfaceDisabled = computed(() => resolvedSurface.value.mode === 'static'
155
+ ? false
156
+ : resolvedSurface.value.disabled ?? false)
157
+
158
+ function emitAction(event: MouseEvent): void {
159
+ emit('action', event)
160
+ }
161
+ </script>
162
+
163
+ <template>
164
+ <div
165
+ v-bind="rootBindings"
166
+ class="s-action-list-item"
167
+ :data-selected="selected"
168
+ :data-tone="tone"
169
+ :data-density="density"
170
+ :data-interactive="resolvedMode !== 'static'"
171
+ :data-mode="resolvedMode"
172
+ :data-disabled="surfaceDisabled || undefined"
173
+ :data-trailing-layout="trailingLayout"
174
+ :data-height-mode="heightMode"
175
+ :data-title-presentation="titlePresentation"
176
+ :data-title-weight="titleWeight"
177
+ :data-detail-presentation="detailPresentation"
178
+ :data-detail-overflow="detailOverflow"
179
+ :style="itemStyle"
180
+ :role="semanticRole"
181
+ >
182
+ <SActionCard
183
+ :surface="resolvedSurface"
184
+ :selected="selected"
185
+ :tone="tone"
186
+ :density="density"
187
+ :padding="surfacePadding"
188
+ :radius="surfaceRadius"
189
+ :fill="heightMode === 'fill' ? 'container' : 'content'"
190
+ @activate="emit('activate', $event)"
191
+ >
192
+ <div class="s-action-list-item__main" :class="{ 's-action-list-item__main--static': resolvedMode === 'static' }">
193
+ <span v-if="icon" class="s-action-list-item__icon" aria-hidden="true">
194
+ <component :is="icon" />
195
+ </span>
196
+ <span class="s-action-list-item__copy">
197
+ <strong class="s-action-list-item__title" :title="titleTooltip">
198
+ <slot name="title">{{ title }}</slot>
199
+ </strong>
200
+ <span v-if="description || $slots.description" class="s-action-list-item__description">
201
+ <slot name="description">{{ description }}</slot>
202
+ </span>
203
+ <span v-if="detail || $slots.detail" class="s-action-list-item__detail">
204
+ <slot name="detail">{{ detail }}</slot>
205
+ </span>
206
+ </span>
207
+ </div>
208
+ </SActionCard>
209
+ <div v-if="hasTrailing" class="s-action-list-item__trailing">
210
+ <slot name="status" />
211
+ <SBadge v-if="statusLabel" :severity="statusSeverity">{{ statusLabel }}</SBadge>
212
+ <SBadge v-for="badge in validatedBadges" :key="badge.id" :severity="badge.severity">
213
+ {{ badge.label }}
214
+ </SBadge>
215
+ <SButton
216
+ v-if="actionLabel"
217
+ size="sm"
218
+ :label="actionLabel"
219
+ :leading-icon="actionIcon"
220
+ :severity="actionSeverity"
221
+ :disabled="surfaceDisabled || actionDisabled"
222
+ @click="emitAction"
223
+ />
224
+ <slot name="actions" :disabled="surfaceDisabled || actionDisabled" :emit-action="emitAction" />
225
+ <component :is="trailingIcon" v-if="trailingIcon" class="s-action-list-item__trailing-icon" aria-hidden="true" />
226
+ </div>
227
+ </div>
228
+ </template>
229
+
230
+ <style lang="postcss" src="./SActionListItem.css" scoped></style>
@@ -0,0 +1,73 @@
1
+ @reference "../../../styles/reference.css";
2
+
3
+ .s-badge {
4
+ @apply inline-flex items-center gap-1 rounded-full font-semibold;
5
+ }
6
+
7
+ .s-badge[data-size='md'] {
8
+ @apply px-2.5 py-0.5 text-xs;
9
+ }
10
+
11
+ .s-badge[data-size='sm'] {
12
+ @apply px-2 py-0.5 text-[11px] leading-4;
13
+ }
14
+
15
+ .s-badge[data-size='xs'] {
16
+ @apply px-1.5 py-0 text-[10px] leading-4;
17
+ }
18
+
19
+ .s-badge__icon {
20
+ @apply inline-flex h-3 w-3 shrink-0 items-center justify-center;
21
+ }
22
+
23
+ .s-badge__icon-graphic {
24
+ @apply h-full w-full;
25
+ }
26
+
27
+ .s-badge[data-icon-motion='spin'] .s-badge__icon-graphic {
28
+ @apply animate-spin;
29
+ }
30
+
31
+ .s-badge__label {
32
+ @apply min-w-0;
33
+ }
34
+
35
+ .s-badge[data-max-width='sm'] {
36
+ max-width: 9.5rem;
37
+ }
38
+
39
+ .s-badge[data-max-width='md'] {
40
+ max-width: 14rem;
41
+ }
42
+
43
+ .s-badge[data-truncate='true'] .s-badge__label {
44
+ @apply overflow-hidden text-ellipsis whitespace-nowrap;
45
+ }
46
+
47
+ /* Severity Colors */
48
+ .s-badge[data-severity='primary'] {
49
+ @apply bg-primary-100 text-primary-800 dark:bg-primary-950 dark:text-primary-50;
50
+ }
51
+ .s-badge[data-severity='secondary'] {
52
+ @apply bg-surface-100 text-surface-800 dark:bg-surface-800 dark:text-surface-50;
53
+ }
54
+ .s-badge[data-severity='success'] {
55
+ @apply bg-success-100 text-success-800 dark:bg-success-950 dark:text-success-50;
56
+ }
57
+ .s-badge[data-severity='info'] {
58
+ @apply bg-info-100 text-info-800 dark:bg-info-950 dark:text-info-50;
59
+ }
60
+ .s-badge[data-severity='warn'] {
61
+ @apply bg-warn-100 text-warn-800 dark:bg-warn-950 dark:text-warn-50;
62
+ }
63
+ .s-badge[data-severity='danger'] {
64
+ @apply bg-danger-100 text-danger-800 dark:bg-danger-950 dark:text-danger-50;
65
+ }
66
+ .s-badge[data-severity='contrast'] {
67
+ @apply bg-surface-800 text-surface-100 dark:bg-surface-200 dark:text-surface-800;
68
+ }
69
+
70
+ /* Dot Mode */
71
+ .s-badge.s-badge--dot {
72
+ @apply w-2.5 h-2.5 p-0;
73
+ }
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <span
3
+ v-bind="ownedAttrs.bindings()"
4
+ class="s-badge"
5
+ :class="{ 's-badge--dot': dot }"
6
+ :data-severity="severity"
7
+ :data-size="size"
8
+ :data-max-width="maxWidth"
9
+ :data-truncate="truncate || undefined"
10
+ :data-icon-motion="iconMotion"
11
+ >
12
+ <template v-if="!dot">
13
+ <span v-if="leadingIcon" class="s-badge__icon" aria-hidden="true">
14
+ <component :is="leadingIcon" class="s-badge__icon-graphic" />
15
+ </span>
16
+ <span class="s-badge__label"><slot /></span>
17
+ </template>
18
+ </span>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import type { Component } from 'vue';
23
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs';
24
+
25
+ defineOptions({ inheritAttrs: false });
26
+
27
+ export type BadgeSeverity = 'primary' | 'secondary' | 'success' | 'info' | 'warn' | 'danger' | 'contrast';
28
+
29
+ export interface Props {
30
+ severity?: BadgeSeverity;
31
+ dot?: boolean;
32
+ size?: 'xs' | 'sm' | 'md';
33
+ leadingIcon?: Component;
34
+ iconMotion?: 'static' | 'spin';
35
+ truncate?: boolean;
36
+ maxWidth?: 'none' | 'sm' | 'md';
37
+ }
38
+
39
+ withDefaults(defineProps<Props>(), {
40
+ severity: 'secondary',
41
+ dot: false,
42
+ size: 'md',
43
+ leadingIcon: undefined,
44
+ iconMotion: 'static',
45
+ truncate: false,
46
+ maxWidth: 'none',
47
+ });
48
+
49
+ const ownedAttrs = useOwnedAttrs({ component: 'SBadge', owner: 'badge root' });
50
+ </script>
51
+
52
+ <style lang="postcss" src="./SBadge.css" scoped></style>
@@ -0,0 +1,56 @@
1
+ @reference "../../../styles/reference.css";
2
+
3
+ .s-code-block {
4
+ @apply relative rounded-md border border-surface-200 bg-surface-0 dark:border-surface-700 dark:bg-surface-800/50;
5
+ width: 100%;
6
+ max-width: 100%;
7
+ min-width: 0;
8
+ overflow: hidden;
9
+ contain: layout paint;
10
+ content-visibility: auto;
11
+ contain-intrinsic-size: 100px;
12
+ }
13
+
14
+ .s-code-block--viewport {
15
+ display: flex;
16
+ flex-direction: column;
17
+ }
18
+
19
+ .s-code-block--interactive {
20
+ contain: none;
21
+ content-visibility: visible;
22
+ contain-intrinsic-size: auto;
23
+ }
24
+
25
+ .s-code-block--radius-none {
26
+ @apply rounded-none;
27
+ }
28
+
29
+ .s-code-block--radius-xl {
30
+ @apply rounded-2xl;
31
+ }
32
+
33
+ .s-code-block-header {
34
+ @apply flex items-center justify-between border-b border-surface-200 px-3 py-1.5 dark:border-surface-700;
35
+ flex: 0 0 auto;
36
+ }
37
+
38
+ .s-code-block-editor-region {
39
+ @apply flex min-h-0 min-w-0;
40
+ }
41
+
42
+ .s-code-block--viewport .s-code-block-editor-region {
43
+ flex: 1 1 auto;
44
+ }
45
+
46
+ .s-code-block-info {
47
+ @apply flex items-center gap-2 text-xs text-surface-500 dark:text-surface-400;
48
+ }
49
+
50
+ .s-code-block-filename {
51
+ @apply font-semibold;
52
+ }
53
+
54
+ .s-code-block-language {
55
+ @apply font-mono;
56
+ }
@@ -0,0 +1,188 @@
1
+ <template>
2
+ <div
3
+ v-bind="ownedAttrs.bindings()"
4
+ class="s-code-block"
5
+ :class="{
6
+ 's-code-block--radius-none': validatedSurfaceRadius === 'none',
7
+ 's-code-block--radius-xl': validatedSurfaceRadius === 'xl',
8
+ 's-code-block--interactive': !validatedReadOnly,
9
+ 's-code-block--viewport': validatedViewport !== 'content',
10
+ }"
11
+ >
12
+ <div v-if="validatedShowHeader" class="s-code-block-header">
13
+ <div class="s-code-block-info">
14
+ <span v-if="validatedLanguage.mode === 'detect'" class="s-code-block-filename">{{ validatedLanguage.filePath }}</span>
15
+ <span class="s-code-block-language">{{ resolvedLanguageId }}</span>
16
+ </div>
17
+ <div class="s-code-block-actions">
18
+ <SButton
19
+ appearance="text"
20
+ severity="secondary"
21
+ size="xs"
22
+ :leading-icon="Copy"
23
+ icon-only
24
+ title="Копировать код"
25
+ aria-label="Копировать код"
26
+ label=""
27
+ @click="copyCode"
28
+ />
29
+ </div>
30
+ </div>
31
+ <div class="s-code-block-editor-region">
32
+ <SCodeEditor
33
+ ref="codeEditor"
34
+ :model-value="props.code"
35
+ :language="validatedLanguage"
36
+ :capabilities="editorCapabilities"
37
+ :read-only="validatedReadOnly"
38
+ :viewport="validatedViewport"
39
+ :aria-label="props.ariaLabel"
40
+ :aria-labelledby="props.ariaLabelledby"
41
+ :placeholder="props.placeholder"
42
+ :next-selection-pos="props.nextSelectionPos"
43
+ @update:model-value="emit('update:code', $event)"
44
+ @cursor-activity="emitCursorActivity"
45
+ @selection-applied="emit('selection-applied', $event)"
46
+ @ready="onReady"
47
+ />
48
+ </div>
49
+ <SAsyncState
50
+ v-if="validatedErrorMessage !== undefined || !isReady"
51
+ :state="validatedErrorMessage === undefined
52
+ ? { status: 'loading', message: 'Загрузка кода...' }
53
+ : { status: 'error', message: validatedErrorMessage }"
54
+ presentation="overlay"
55
+ />
56
+ </div>
57
+ </template>
58
+
59
+ <script setup lang="ts">
60
+ import { computed, ref } from 'vue';
61
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs';
62
+ import { validateBoolean, validateExactString, validateNonEmptyString } from '../../../internal/runtimeContract';
63
+ import { validateCodeEditorLanguage, validateCodeEditorViewport } from '../../../internal/codeEditorContract';
64
+ import { resolveCodeLanguageKey } from '../codeLanguages';
65
+ import { useNotifier } from '../../../composables/useNotifier';
66
+ import { useClipboard } from '../../../composables/useClipboard';
67
+ import SButton from '../controls/SButton.vue';
68
+ import SCodeEditor, {
69
+ type SCodeEditorCapabilities,
70
+ type SCodeEditorCursorActivity,
71
+ type SCodeEditorLanguage,
72
+ type SCodeEditorReadyPayload,
73
+ type SCodeEditorSelectionAppliedPayload,
74
+ type SCodeEditorViewport,
75
+ } from './SCodeEditor.vue';
76
+ import { Copy } from '../../icons/sputnigUiIcons';
77
+ import SAsyncState from '../feedback/SAsyncState.vue';
78
+
79
+ defineOptions({ inheritAttrs: false });
80
+
81
+ type AccessibleName =
82
+ | { ariaLabel: string; ariaLabelledby?: never }
83
+ | { ariaLabel?: never; ariaLabelledby: string };
84
+
85
+ /** Public adapter contract над canonical SCodeEditor leaf. / Public adapter contract over the canonical SCodeEditor leaf. */
86
+ export type Props = AccessibleName & {
87
+ code: string;
88
+ language: SCodeEditorLanguage;
89
+ showHeader?: boolean;
90
+ showLineNumbers?: boolean;
91
+ readOnly?: boolean;
92
+ interceptKeys?: (e: KeyboardEvent) => boolean;
93
+ tabSize?: number;
94
+ tabKeyBehavior?: SCodeEditorCapabilities['indentation']['keyBehavior'];
95
+ nextSelectionPos?: number | null;
96
+ surfaceRadius?: 'default' | 'none' | 'xl';
97
+ viewport?: SCodeEditorViewport;
98
+ placeholder?: string;
99
+ errorMessage?: string;
100
+ };
101
+
102
+ const props = withDefaults(defineProps<Props>(), {
103
+ showHeader: true,
104
+ showLineNumbers: true,
105
+ readOnly: true,
106
+ interceptKeys: undefined,
107
+ tabSize: 2,
108
+ tabKeyBehavior: 'focus-next',
109
+ nextSelectionPos: null,
110
+ surfaceRadius: 'default',
111
+ viewport: 'content',
112
+ placeholder: undefined,
113
+ errorMessage: undefined,
114
+ });
115
+
116
+ const isReady = ref(false);
117
+ const codeEditor = ref<InstanceType<typeof SCodeEditor> | null>(null);
118
+ const ownedAttrs = useOwnedAttrs({ component: 'SCodeBlock', owner: 'code block root' });
119
+ const emit = defineEmits<{
120
+ (e: 'cursor-activity', payload: { pos: number; lineFrom: number; lineTo: number; lineText: string; cursorCol: number }): void
121
+ (e: 'update:code', value: string): void
122
+ (e: 'selection-applied', payload: SCodeEditorSelectionAppliedPayload): void
123
+ (e: 'ready', payload: SCodeEditorReadyPayload): void
124
+ }>();
125
+
126
+ const validatedShowHeader = computed(() => validateBoolean('SCodeBlock', 'showHeader', props.showHeader));
127
+ const validatedReadOnly = computed(() => validateBoolean('SCodeBlock', 'readOnly', props.readOnly));
128
+ const validatedSurfaceRadius = computed(() => validateExactString(
129
+ 'SCodeBlock', 'surfaceRadius', props.surfaceRadius, ['default', 'none', 'xl'] as const,
130
+ ));
131
+ const validatedViewport = computed(() => validateCodeEditorViewport('SCodeBlock', props.viewport));
132
+ const validatedLanguage = computed(() => validateCodeEditorLanguage('SCodeBlock', props.language));
133
+ const resolvedLanguageId = computed(() => resolveCodeLanguageKey(validatedLanguage.value));
134
+ const validatedErrorMessage = computed(() => props.errorMessage === undefined
135
+ ? undefined
136
+ : validateNonEmptyString('SCodeBlock', 'errorMessage', props.errorMessage));
137
+ const editorCapabilities = computed<SCodeEditorCapabilities>(() => ({
138
+ lineNumbers: props.showLineNumbers,
139
+ indentation: { tabSize: props.tabSize, keyBehavior: props.tabKeyBehavior },
140
+ ...(props.interceptKeys === undefined ? {} : { keyInterceptor: props.interceptKeys }),
141
+ cursorActivity: 'always',
142
+ }));
143
+
144
+ const emitCursorActivity = (payload: SCodeEditorCursorActivity) => emit('cursor-activity', {
145
+ pos: payload.pos,
146
+ lineFrom: payload.lineFrom,
147
+ lineTo: payload.lineTo,
148
+ lineText: payload.lineText,
149
+ cursorCol: payload.pos - payload.lineFrom,
150
+ });
151
+
152
+ const onReady = (payload: SCodeEditorReadyPayload): void => {
153
+ isReady.value = true;
154
+ emit('ready', payload);
155
+ };
156
+
157
+ const applySelection = (position: number): void => {
158
+ const editor = codeEditor.value;
159
+ if (!editor) throw new Error('SCodeBlock: editor не готов; дождитесь ready. / SCodeBlock: editor is not ready; wait for ready.');
160
+ editor.applySelection(position);
161
+ };
162
+
163
+ const focus = (): void => {
164
+ const editor = codeEditor.value;
165
+ if (!editor) throw new Error('SCodeBlock: editor не готов; дождитесь ready. / SCodeBlock: editor is not ready; wait for ready.');
166
+ editor.focus();
167
+ };
168
+
169
+ defineExpose({ applySelection, focus });
170
+ const notifier = useNotifier();
171
+ const { copyText } = useClipboard();
172
+ const copyCode = async () => {
173
+ const result = await copyText(props.code);
174
+ if (result.status === 'copied') {
175
+ notifier.success({ message: 'Код скопирован' });
176
+ return;
177
+ }
178
+ notifier.danger({
179
+ message: result.status === 'unavailable'
180
+ ? 'Копирование недоступно в этом браузере'
181
+ : result.status === 'empty'
182
+ ? 'Нет кода для копирования'
183
+ : `Ошибка копирования. ${result.error.message}`,
184
+ });
185
+ };
186
+ </script>
187
+
188
+ <style lang="postcss" src="./SCodeBlock.css" scoped></style>