@pgcorp/ui-kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/docs/accessibility.md +27 -0
- package/docs/getting-started.md +80 -0
- package/docs/licensing.md +30 -0
- package/docs/public-api.md +39 -0
- package/docs/security.md +21 -0
- package/docs/theming.md +35 -0
- package/docs/versioning.md +23 -0
- package/index.ts +3 -0
- package/package.json +592 -0
- package/src/components/icons/_internal/generatedSputnigUiIconMarkup.ts +971 -0
- package/src/components/icons/sputnigUiIcons.ts +530 -0
- package/src/components/layout/SAppShell.vue +232 -0
- package/src/components/layout/SDockRegion.vue +200 -0
- package/src/components/layout/SResizeHandle.vue +251 -0
- package/src/components/layout/SSideMenuButton.vue +104 -0
- package/src/components/layout/SSideMenuRail.vue +47 -0
- package/src/components/layout/SStack.css +25 -0
- package/src/components/layout/SStack.vue +57 -0
- package/src/components/layout/SStatusBarShell.css +15 -0
- package/src/components/layout/SStatusBarShell.vue +26 -0
- package/src/components/layout/SWorkbenchLayout.vue +53 -0
- package/src/components/layout/SWorkbenchPanelShell.css +161 -0
- package/src/components/layout/SWorkbenchPanelShell.vue +286 -0
- package/src/components/layout/_internal/SAppBrand.css +45 -0
- package/src/components/layout/_internal/SAppBrand.vue +37 -0
- package/src/components/layout/_internal/SAppBrandContent.vue +30 -0
- package/src/components/layout/types.ts +28 -0
- package/src/components/shared/_internal/SDialogSurface.css +127 -0
- package/src/components/shared/_internal/SDialogSurface.vue +117 -0
- package/src/components/shared/_internal/SDisclosureSummary.css +92 -0
- package/src/components/shared/_internal/SDisclosureSummary.vue +121 -0
- package/src/components/shared/_internal/SListboxGroup.css +18 -0
- package/src/components/shared/_internal/SListboxGroup.vue +24 -0
- package/src/components/shared/_internal/SMenuItem.css +112 -0
- package/src/components/shared/_internal/SMenuItem.vue +213 -0
- package/src/components/shared/_internal/SMenuLayer.css +21 -0
- package/src/components/shared/_internal/SMenuLayer.vue +48 -0
- package/src/components/shared/_internal/SMenuSeparator.css +5 -0
- package/src/components/shared/_internal/SMenuSeparator.vue +13 -0
- package/src/components/shared/_internal/SToastItem.css +65 -0
- package/src/components/shared/_internal/SToastItem.vue +58 -0
- package/src/components/shared/_internal/STreeNode.vue +761 -0
- package/src/components/shared/_internal/dialogSurface.ts +105 -0
- package/src/components/shared/_internal/focusTrap.ts +40 -0
- package/src/components/shared/_internal/listboxContext.ts +16 -0
- package/src/components/shared/_internal/markerContract.ts +10 -0
- package/src/components/shared/_internal/menuContext.ts +23 -0
- package/src/components/shared/_internal/selectionContract.ts +239 -0
- package/src/components/shared/_internal/sidebarGroupContext.ts +31 -0
- package/src/components/shared/_internal/tabsContext.ts +57 -0
- package/src/components/shared/_internal/treeRuntime.ts +203 -0
- package/src/components/shared/_internal/useSelectionRoving.ts +162 -0
- package/src/components/shared/codeLanguages.ts +168 -0
- package/src/components/shared/complex/SKanbanCard.css +8 -0
- package/src/components/shared/complex/SKanbanCard.vue +76 -0
- package/src/components/shared/complex/SKanbanLane.vue +82 -0
- package/src/components/shared/complex/STree.css +24 -0
- package/src/components/shared/complex/STree.vue +288 -0
- package/src/components/shared/complex/treeAdapter.ts +210 -0
- package/src/components/shared/complex/types.ts +102 -0
- package/src/components/shared/containers/SCloudChrome.css +103 -0
- package/src/components/shared/containers/SCloudChrome.vue +87 -0
- package/src/components/shared/containers/SCloudChromeEmpty.css +10 -0
- package/src/components/shared/containers/SCloudChromeEmpty.vue +18 -0
- package/src/components/shared/containers/SCloudChromeRow.css +78 -0
- package/src/components/shared/containers/SCloudChromeRow.vue +108 -0
- package/src/components/shared/containers/SCollapsiblePanel.css +57 -0
- package/src/components/shared/containers/SCollapsiblePanel.vue +360 -0
- package/src/components/shared/containers/SDrawer.css +60 -0
- package/src/components/shared/containers/SDrawer.vue +86 -0
- package/src/components/shared/containers/SFormGrid.css +26 -0
- package/src/components/shared/containers/SFormGrid.vue +46 -0
- package/src/components/shared/containers/SFormGridItem.css +18 -0
- package/src/components/shared/containers/SFormGridItem.vue +29 -0
- package/src/components/shared/containers/SLeftSidebar.vue +74 -0
- package/src/components/shared/containers/SModal.css +27 -0
- package/src/components/shared/containers/SModal.vue +105 -0
- package/src/components/shared/containers/SPageHeader.css +66 -0
- package/src/components/shared/containers/SPageHeader.vue +45 -0
- package/src/components/shared/containers/SPanel.css +134 -0
- package/src/components/shared/containers/SPanel.vue +169 -0
- package/src/components/shared/containers/SPopover.css +35 -0
- package/src/components/shared/containers/SPopover.vue +551 -0
- package/src/components/shared/containers/SSidebarGroup.css +12 -0
- package/src/components/shared/containers/SSidebarGroup.vue +443 -0
- package/src/components/shared/containers/SSidebarSection.css +41 -0
- package/src/components/shared/containers/SSidebarSection.vue +268 -0
- package/src/components/shared/containers/disclosureSummary.ts +48 -0
- package/src/components/shared/containers/popover.ts +72 -0
- package/src/components/shared/containers/sidebar.ts +206 -0
- package/src/components/shared/controls/SButton.css +274 -0
- package/src/components/shared/controls/SButton.vue +211 -0
- package/src/components/shared/controls/SCheckbox.css +65 -0
- package/src/components/shared/controls/SCheckbox.vue +130 -0
- package/src/components/shared/controls/SCheckboxGroup.vue +134 -0
- package/src/components/shared/controls/SChoiceCards.css +29 -0
- package/src/components/shared/controls/SChoiceCards.vue +145 -0
- package/src/components/shared/controls/SColorSelect.vue +146 -0
- package/src/components/shared/controls/SComboboxTrigger.css +35 -0
- package/src/components/shared/controls/SComboboxTrigger.vue +122 -0
- package/src/components/shared/controls/SContextMenu.css +11 -0
- package/src/components/shared/controls/SContextMenu.vue +186 -0
- package/src/components/shared/controls/SContextToggleButton.vue +64 -0
- package/src/components/shared/controls/SDragHandle.vue +100 -0
- package/src/components/shared/controls/SDropdownMenu.css +5 -0
- package/src/components/shared/controls/SDropdownMenu.vue +188 -0
- package/src/components/shared/controls/SField.css +79 -0
- package/src/components/shared/controls/SField.vue +89 -0
- package/src/components/shared/controls/SFieldGroup.css +58 -0
- package/src/components/shared/controls/SFieldGroup.vue +117 -0
- package/src/components/shared/controls/SFieldLabel.css +12 -0
- package/src/components/shared/controls/SFieldLabel.vue +51 -0
- package/src/components/shared/controls/SInlineTokenEditor.css +61 -0
- package/src/components/shared/controls/SInlineTokenEditor.vue +1081 -0
- package/src/components/shared/controls/SInputText.css +66 -0
- package/src/components/shared/controls/SInputText.vue +321 -0
- package/src/components/shared/controls/SInteractiveSurface.css +72 -0
- package/src/components/shared/controls/SInteractiveSurface.vue +326 -0
- package/src/components/shared/controls/SLink.css +114 -0
- package/src/components/shared/controls/SLink.ts +85 -0
- package/src/components/shared/controls/SLink.vue +114 -0
- package/src/components/shared/controls/SListbox.css +31 -0
- package/src/components/shared/controls/SListbox.vue +388 -0
- package/src/components/shared/controls/SListboxOption.css +63 -0
- package/src/components/shared/controls/SListboxOption.vue +85 -0
- package/src/components/shared/controls/SMarker.css +19 -0
- package/src/components/shared/controls/SMarker.vue +33 -0
- package/src/components/shared/controls/SMenuSurface.css +20 -0
- package/src/components/shared/controls/SMenuSurface.vue +333 -0
- package/src/components/shared/controls/SSegmentedControl.css +38 -0
- package/src/components/shared/controls/SSegmentedControl.vue +134 -0
- package/src/components/shared/controls/SSelect.css +12 -0
- package/src/components/shared/controls/SSelect.vue +338 -0
- package/src/components/shared/controls/STextarea.css +31 -0
- package/src/components/shared/controls/STextarea.vue +316 -0
- package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +224 -0
- package/src/components/shared/controls/menu.ts +178 -0
- package/src/components/shared/data-display/SActionCard.css +6 -0
- package/src/components/shared/data-display/SActionCard.vue +49 -0
- package/src/components/shared/data-display/SActionList.css +15 -0
- package/src/components/shared/data-display/SActionList.vue +34 -0
- package/src/components/shared/data-display/SActionListItem.css +114 -0
- package/src/components/shared/data-display/SActionListItem.vue +230 -0
- package/src/components/shared/data-display/SBadge.css +73 -0
- package/src/components/shared/data-display/SBadge.vue +52 -0
- package/src/components/shared/data-display/SCodeBlock.css +56 -0
- package/src/components/shared/data-display/SCodeBlock.vue +188 -0
- package/src/components/shared/data-display/SCodeEditor.css +178 -0
- package/src/components/shared/data-display/SCodeEditor.vue +274 -0
- package/src/components/shared/data-display/SCodeSearchPanel.vue +327 -0
- package/src/components/shared/data-display/SCopyField.vue +77 -0
- package/src/components/shared/data-display/SDocBlock.css +136 -0
- package/src/components/shared/data-display/SDocBlock.vue +358 -0
- package/src/components/shared/data-display/SEmptyState.css +42 -0
- package/src/components/shared/data-display/SEmptyState.vue +79 -0
- package/src/components/shared/data-display/SExpandableText.vue +314 -0
- package/src/components/shared/data-display/SJsonTree.css +6 -0
- package/src/components/shared/data-display/SJsonTree.vue +111 -0
- package/src/components/shared/data-display/SKeyValueGrid.css +37 -0
- package/src/components/shared/data-display/SKeyValueGrid.vue +277 -0
- package/src/components/shared/data-display/SLinkedSystemsList.vue +270 -0
- package/src/components/shared/data-display/SMessage.css +88 -0
- package/src/components/shared/data-display/SMessage.vue +152 -0
- package/src/components/shared/data-display/SMetricCard.css +32 -0
- package/src/components/shared/data-display/SMetricCard.vue +117 -0
- package/src/components/shared/data-display/SProgressBar.css +84 -0
- package/src/components/shared/data-display/SProgressBar.vue +114 -0
- package/src/components/shared/data-display/SProgressIndicator.css +59 -0
- package/src/components/shared/data-display/SProgressIndicator.vue +104 -0
- package/src/components/shared/data-display/SSectionHeader.css +84 -0
- package/src/components/shared/data-display/SSectionHeader.vue +58 -0
- package/src/components/shared/data-display/SStatus.css +90 -0
- package/src/components/shared/data-display/SStatus.vue +79 -0
- package/src/components/shared/data-display/STable.css +83 -0
- package/src/components/shared/data-display/STable.vue +339 -0
- package/src/components/shared/data-display/SToastContainer.css +53 -0
- package/src/components/shared/data-display/SToastContainer.vue +31 -0
- package/src/components/shared/data-display/STooltip.css +114 -0
- package/src/components/shared/data-display/STooltip.vue +550 -0
- package/src/components/shared/data-display/STooltipAnchor.css +21 -0
- package/src/components/shared/data-display/STooltipAnchor.vue +119 -0
- package/src/components/shared/data-display/SVirtualList.css +24 -0
- package/src/components/shared/data-display/SVirtualList.ts +19 -0
- package/src/components/shared/data-display/SVirtualList.vue +361 -0
- package/src/components/shared/data-display/json.ts +136 -0
- package/src/components/shared/data-display/table.ts +76 -0
- package/src/components/shared/database/SDataGrid.css +3 -0
- package/src/components/shared/database/SDataGrid.vue +464 -0
- package/src/components/shared/database/SSqlEditor.vue +363 -0
- package/src/components/shared/database/dataGrid.ts +61 -0
- package/src/components/shared/feedback/SAsyncState.css +26 -0
- package/src/components/shared/feedback/SAsyncState.vue +190 -0
- package/src/components/shared/graph/SGraphViewport.ts +1 -0
- package/src/components/shared/graph/SGraphViewport.vue +858 -0
- package/src/components/shared/graph/_internal/graphViewportContract.ts +362 -0
- package/src/components/shared/markdown.ts +169 -0
- package/src/components/shared/navigation/SBottomNav.vue +692 -0
- package/src/components/shared/navigation/SBreadcrumbs.css +26 -0
- package/src/components/shared/navigation/SBreadcrumbs.vue +155 -0
- package/src/components/shared/navigation/SCatalogNavigator.css +100 -0
- package/src/components/shared/navigation/SCatalogNavigator.vue +308 -0
- package/src/components/shared/navigation/STab.vue +363 -0
- package/src/components/shared/navigation/STabList.vue +442 -0
- package/src/components/shared/navigation/STabPanel.vue +165 -0
- package/src/components/shared/navigation/STabPanels.vue +33 -0
- package/src/components/shared/navigation/STabs.vue +617 -0
- package/src/components/shared/navigation/SWizardSteps.css +69 -0
- package/src/components/shared/navigation/SWizardSteps.vue +171 -0
- package/src/components/shared/navigation/_internal/SCatalogNavigatorGroup.vue +63 -0
- package/src/components/shared/persona/SPersonaProjectPicker.css +36 -0
- package/src/components/shared/persona/SPersonaProjectPicker.vue +361 -0
- package/src/components/shared/persona/SPersonaRunWorkbench.css +109 -0
- package/src/components/shared/persona/SPersonaRunWorkbench.vue +549 -0
- package/src/components/shared/persona/_internal/SPersonaRunConsole.css +40 -0
- package/src/components/shared/persona/_internal/SPersonaRunConsole.vue +298 -0
- package/src/components/shared/persona/_internal/inventory.ts +110 -0
- package/src/components/shared/persona/_internal/labels.ts +79 -0
- package/src/components/shared/persona/presentation.ts +371 -0
- package/src/components/shared/persona/types.ts +256 -0
- package/src/composables/uiPreferencesReset.ts +52 -0
- package/src/composables/useClipboard.ts +37 -0
- package/src/composables/useDialogOverlay.ts +85 -0
- package/src/composables/useDisclosure.ts +40 -0
- package/src/composables/useFloatingPosition.ts +245 -0
- package/src/composables/useNotifier.ts +26 -0
- package/src/composables/useSidebarPanelState.ts +610 -0
- package/src/internal/codeEditorContract.ts +110 -0
- package/src/internal/codeEditorExtensions.ts +27 -0
- package/src/internal/feedbackPresentation.ts +136 -0
- package/src/internal/fieldSurface.ts +59 -0
- package/src/internal/focusNavigation.ts +69 -0
- package/src/internal/inlineTokenEditorContract.ts +136 -0
- package/src/internal/interactiveElement.ts +33 -0
- package/src/internal/layerStack.ts +202 -0
- package/src/internal/linkTarget.ts +256 -0
- package/src/internal/ownedAttrs.ts +464 -0
- package/src/internal/passiveContentContract.ts +129 -0
- package/src/internal/pointerInteractionLease.ts +81 -0
- package/src/internal/runtimeContract.ts +194 -0
- package/src/internal/runtimeTheme.ts +159 -0
- package/src/internal/semanticSizing.ts +60 -0
- package/src/internal/useMenuLayer.ts +129 -0
- package/src/stores/useNotifierStore.ts +69 -0
- package/src/styles/reference.css +4 -0
- package/src/styles/style.css +151 -0
- package/src/styles/tokens.css +836 -0
- package/src/theme.ts +449 -0
- package/src/types/highlight-js-lib.d.ts +76 -0
- package/src/variants.ts +15 -0
|
@@ -0,0 +1,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
|
+
}
|