@pgcorp/ui-kit 0.8.1 → 0.10.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/README.md +332 -9
- package/docs/getting-started.md +135 -0
- package/docs/public-api.md +53 -4
- package/docs/rich-document.md +216 -0
- package/docs/scroll-viewport.md +167 -0
- package/docs/theming.md +17 -0
- package/package.json +108 -50
- package/src/components/layout/SAppShell.vue +3 -1
- package/src/components/layout/SDockRegion.vue +5 -3
- package/src/components/layout/SResizeHandle.vue +45 -5
- package/src/components/layout/SWorkbenchPanelShell.vue +4 -2
- package/src/components/shared/_internal/SDialogSurface.css +23 -0
- package/src/components/shared/_internal/SDialogSurface.vue +9 -2
- package/src/components/shared/_internal/SToastItem.css +4 -0
- package/src/components/shared/_internal/SToastItem.vue +44 -8
- package/src/components/shared/_internal/STreeNode.vue +24 -11
- package/src/components/shared/_internal/dialogSurface.ts +7 -1
- package/src/components/shared/complex/SKanbanLane.vue +4 -2
- package/src/components/shared/containers/SCloudChromeRow.vue +3 -2
- package/src/components/shared/containers/SDrawer.css +11 -2
- package/src/components/shared/containers/SDrawer.vue +48 -9
- package/src/components/shared/containers/SModal.vue +5 -2
- package/src/components/shared/containers/SPanel.vue +4 -2
- package/src/components/shared/containers/SPopover.vue +7 -3
- package/src/components/shared/containers/SUiKitLocaleProvider.vue +57 -0
- package/src/components/shared/controls/SButton.css +14 -10
- package/src/components/shared/controls/SCombobox.vue +8 -6
- package/src/components/shared/controls/SComboboxTrigger.css +7 -4
- package/src/components/shared/controls/SComboboxTrigger.vue +5 -2
- package/src/components/shared/controls/SContextToggleButton.vue +6 -4
- package/src/components/shared/controls/SDragHandle.vue +8 -4
- package/src/components/shared/controls/SDropdownMenu.vue +7 -4
- package/src/components/shared/controls/SInputText.css +5 -4
- package/src/components/shared/controls/SListbox.vue +4 -2
- package/src/components/shared/controls/SSelect.vue +4 -2
- package/src/components/shared/controls/STextarea.ts +13 -0
- package/src/components/shared/controls/STextarea.vue +391 -37
- package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +4 -2
- package/src/components/shared/data-display/SChart.vue +7 -5
- package/src/components/shared/data-display/SChip.vue +3 -1
- package/src/components/shared/data-display/SCodeBlock.css +4 -0
- package/src/components/shared/data-display/SCodeBlock.vue +46 -22
- package/src/components/shared/data-display/SCodeEditor.css +17 -0
- package/src/components/shared/data-display/SCodeEditor.vue +173 -8
- package/src/components/shared/data-display/SCodeSearchPanel.vue +48 -33
- package/src/components/shared/data-display/SCodeWorkspaceEditor.css +13 -0
- package/src/components/shared/data-display/SCodeWorkspaceEditor.vue +355 -0
- package/src/components/shared/data-display/SControlledToastViewport.css +89 -0
- package/src/components/shared/data-display/SControlledToastViewport.vue +205 -0
- package/src/components/shared/data-display/SCopyField.vue +12 -10
- package/src/components/shared/data-display/SDiffViewer.css +122 -0
- package/src/components/shared/data-display/SDiffViewer.vue +499 -0
- package/src/components/shared/data-display/SDocBlock.vue +99 -71
- package/src/components/shared/data-display/SExpandableText.vue +10 -8
- package/src/components/shared/data-display/SJsonTree.vue +4 -2
- package/src/components/shared/data-display/SLinkedSystemsList.vue +4 -2
- package/src/components/shared/data-display/SMessage.vue +6 -6
- package/src/components/shared/data-display/SRichDocument.css +8 -0
- package/src/components/shared/data-display/SRichDocument.vue +65 -0
- package/src/components/shared/data-display/SScrollViewport.css +53 -0
- package/src/components/shared/data-display/SScrollViewport.vue +470 -0
- package/src/components/shared/data-display/STable.vue +38 -19
- package/src/components/shared/data-display/SToastContainer.vue +22 -17
- package/src/components/shared/data-display/SVirtualList.ts +33 -0
- package/src/components/shared/data-display/SVirtualList.vue +321 -36
- package/src/components/shared/data-display/_internal/SCodeTokenLine.css +72 -0
- package/src/components/shared/data-display/_internal/SCodeTokenLine.vue +79 -0
- package/src/components/shared/data-display/_internal/SRichBlockNode.css +85 -0
- package/src/components/shared/data-display/_internal/SRichBlockNode.vue +282 -0
- package/src/components/shared/data-display/_internal/SRichInlineNode.css +6 -0
- package/src/components/shared/data-display/_internal/SRichInlineNode.vue +76 -0
- package/src/components/shared/data-display/_internal/SRichMath.css +42 -0
- package/src/components/shared/data-display/_internal/SRichMath.vue +102 -0
- package/src/components/shared/data-display/codeEditor.ts +200 -0
- package/src/components/shared/data-display/codeWorkspace.ts +233 -0
- package/src/components/shared/data-display/diff.ts +541 -0
- package/src/components/shared/data-display/richDocument.ts +876 -0
- package/src/components/shared/data-display/scrollViewport.ts +47 -0
- package/src/components/shared/data-display/toast.ts +194 -0
- package/src/components/shared/database/SDataGrid.vue +7 -4
- package/src/components/shared/database/SSqlEditor.vue +39 -39
- package/src/components/shared/feedback/SAsyncState.vue +3 -1
- package/src/components/shared/graph/SGraphViewport.vue +11 -9
- package/src/components/shared/navigation/SBreadcrumbs.vue +8 -5
- package/src/components/shared/navigation/SCatalogNavigator.vue +26 -20
- package/src/components/shared/navigation/STabList.vue +10 -6
- package/src/components/shared/navigation/STabs.vue +15 -11
- package/src/components/shared/navigation/SWizardSteps.vue +4 -2
- package/src/components/shared/persona/SPersonaProjectPicker.vue +31 -27
- package/src/components/shared/persona/SPersonaRunWorkbench.vue +39 -33
- package/src/components/shared/persona/_internal/SPersonaRunConsole.vue +8 -6
- package/src/components/shared/persona/_internal/labels.ts +66 -57
- package/src/components/shared/persona/presentation.ts +140 -110
- package/src/composables/useFloatingPosition.ts +39 -2
- package/src/composables/useUiKitLocale.ts +17 -0
- package/src/internal/codeHighlight.ts +110 -0
- package/src/internal/diffEngine.ts +464 -0
- package/src/internal/diffRuntime.ts +189 -0
- package/src/internal/feedbackPresentation.ts +33 -14
- package/src/internal/localeContext.ts +22 -0
- package/src/internal/ownedAttrs.ts +8 -0
- package/src/internal/runtimeContract.ts +11 -9
- package/src/internal/semanticSizing.ts +13 -0
- package/src/locales/catalog.ts +634 -0
- package/src/locales/locale.ts +472 -0
- package/src/locales/messages.generated.ts +1826 -0
- package/src/locales/messages.ts +150 -0
- package/src/stores/useNotifierStore.ts +61 -44
- package/src/styles/tokens.css +23 -2
- package/src/theme.ts +59 -32
- package/src/workers/diff.worker.ts +73 -0
- package/src/workers/diffProtocol.ts +155 -0
- package/src/components/shared/data-display/SToastContainer.css +0 -53
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
:role="liveAttributes.role"
|
|
6
6
|
:aria-live="liveAttributes.ariaLive"
|
|
7
7
|
:aria-atomic="liveAttributes.ariaAtomic"
|
|
8
|
+
@mouseenter="emit('lifetime-pause', notification.id, 'pointer')"
|
|
9
|
+
@mouseleave="emit('lifetime-resume', notification.id, 'pointer')"
|
|
10
|
+
@focusin="emit('lifetime-pause', notification.id, 'focus')"
|
|
11
|
+
@focusout="onFocusOut"
|
|
8
12
|
>
|
|
9
13
|
<div class="s-toast-icon">
|
|
10
14
|
<component
|
|
@@ -20,40 +24,61 @@
|
|
|
20
24
|
<p v-if="notification.message" class="s-toast-body">
|
|
21
25
|
{{ notification.message }}
|
|
22
26
|
</p>
|
|
27
|
+
<div v-if="notification.actions?.length" class="s-toast-actions">
|
|
28
|
+
<SButton
|
|
29
|
+
v-for="action in notification.actions"
|
|
30
|
+
:key="action.id"
|
|
31
|
+
size="xs"
|
|
32
|
+
severity="secondary"
|
|
33
|
+
:appearance="action.appearance ?? 'soft'"
|
|
34
|
+
:label="action.label"
|
|
35
|
+
@click="emit('action', { notificationId: notification.id, actionId: action.id })"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
23
38
|
</div>
|
|
24
39
|
|
|
25
|
-
<div class="s-toast-close">
|
|
40
|
+
<div v-if="notification.dismissible !== false" class="s-toast-close">
|
|
26
41
|
<SButton
|
|
27
|
-
size="
|
|
42
|
+
size="xs"
|
|
28
43
|
appearance="ghost"
|
|
29
44
|
icon-only
|
|
30
45
|
:leading-icon="X"
|
|
31
|
-
aria-label="
|
|
46
|
+
:aria-label="t('feedback.closeNotification')"
|
|
32
47
|
@click="emit('dismiss', notification.id)"
|
|
33
48
|
/>
|
|
34
49
|
</div>
|
|
35
50
|
</div>
|
|
36
51
|
</template>
|
|
37
52
|
|
|
38
|
-
<script setup lang="ts">
|
|
53
|
+
<script setup lang="ts" generic="Id extends SToastId = string, ActionId extends SToastActionId = string">
|
|
39
54
|
import { computed } from 'vue'
|
|
55
|
+
import { useUiKitLocale } from '../../../composables/useUiKitLocale'
|
|
40
56
|
|
|
41
57
|
import { X } from '../../icons/sputnigUiIcons'
|
|
42
58
|
import { resolveFeedbackLive, resolveFeedbackPresentation } from '../../../internal/feedbackPresentation'
|
|
43
59
|
import { resolveSemanticToneBinding } from '../../../internal/semanticTone'
|
|
44
|
-
import type {
|
|
60
|
+
import type {
|
|
61
|
+
SToastActionId,
|
|
62
|
+
SToastActionIntent,
|
|
63
|
+
SToastId,
|
|
64
|
+
SToastPresentation,
|
|
65
|
+
} from '../data-display/toast'
|
|
45
66
|
import SButton from '../controls/SButton.vue'
|
|
46
67
|
|
|
47
68
|
defineOptions({ inheritAttrs: false })
|
|
48
69
|
|
|
49
70
|
const props = defineProps<{
|
|
50
|
-
notification:
|
|
71
|
+
notification: SToastPresentation<Id, ActionId>
|
|
51
72
|
}>()
|
|
52
73
|
|
|
53
74
|
const emit = defineEmits<{
|
|
54
|
-
dismiss: [id:
|
|
75
|
+
dismiss: [id: Id]
|
|
76
|
+
action: [intent: SToastActionIntent<Id, ActionId>]
|
|
77
|
+
'lifetime-pause': [id: Id, reason: 'pointer' | 'focus']
|
|
78
|
+
'lifetime-resume': [id: Id, reason: 'pointer' | 'focus']
|
|
55
79
|
}>()
|
|
56
|
-
const
|
|
80
|
+
const { t } = useUiKitLocale()
|
|
81
|
+
const presentation = computed(() => resolveFeedbackPresentation(props.notification.severity, t))
|
|
57
82
|
const toneBinding = computed(() => resolveSemanticToneBinding(
|
|
58
83
|
'SToastItem',
|
|
59
84
|
'notification.severity',
|
|
@@ -61,5 +86,16 @@ const toneBinding = computed(() => resolveSemanticToneBinding(
|
|
|
61
86
|
['info', 'success', 'warn', 'danger'] as const,
|
|
62
87
|
))
|
|
63
88
|
const liveAttributes = computed(() => resolveFeedbackLive(props.notification.severity, 'auto'))
|
|
89
|
+
|
|
90
|
+
function onFocusOut(event: FocusEvent): void {
|
|
91
|
+
const currentTarget = event.currentTarget
|
|
92
|
+
const relatedTarget = event.relatedTarget
|
|
93
|
+
if (
|
|
94
|
+
currentTarget instanceof HTMLElement
|
|
95
|
+
&& relatedTarget instanceof Node
|
|
96
|
+
&& currentTarget.contains(relatedTarget)
|
|
97
|
+
) return
|
|
98
|
+
emit('lifetime-resume', props.notification.id, 'focus')
|
|
99
|
+
}
|
|
64
100
|
</script>
|
|
65
101
|
<style lang="postcss" src="./SToastItem.css" scoped></style>
|
|
@@ -217,6 +217,7 @@
|
|
|
217
217
|
|
|
218
218
|
<script setup lang="ts" generic="T extends object, K extends SelectionKey">
|
|
219
219
|
import { computed, defineComponent, h, inject, onBeforeUnmount, onMounted, provide, ref } from 'vue'
|
|
220
|
+
import { useUiKitLocale } from '../../../composables/useUiKitLocale'
|
|
220
221
|
import type { PropType } from 'vue'
|
|
221
222
|
import { Check, ChevronDown, ChevronRight, Minus } from '../../icons/sputnigUiIcons'
|
|
222
223
|
import {
|
|
@@ -247,6 +248,7 @@ const props = defineProps<{
|
|
|
247
248
|
position: number
|
|
248
249
|
setSize: number
|
|
249
250
|
}>()
|
|
251
|
+
const { t } = useUiKitLocale()
|
|
250
252
|
|
|
251
253
|
const slots = defineSlots<{
|
|
252
254
|
'col-left': (props: STreeNodeContentScope<T, K>) => unknown
|
|
@@ -336,12 +338,14 @@ const nodeIndent = computed(() => {
|
|
|
336
338
|
if (runtime.density.value === 'compact') return `${props.level * 0.85 + 0.25}rem`
|
|
337
339
|
return `${props.level === 0 ? 1 : props.level * 1.5}rem`
|
|
338
340
|
})
|
|
339
|
-
const toggleLabel = computed(() =>
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
341
|
+
const toggleLabel = computed(() => t(
|
|
342
|
+
isExpanded.value ? 'tree.collapseNode' : 'tree.expandNode',
|
|
343
|
+
{ label: nodeLabel.value },
|
|
344
|
+
))
|
|
345
|
+
const selectionLabel = computed(() => t(
|
|
346
|
+
isSelected.value || isPartiallySelected.value ? 'tree.deselectNode' : 'tree.selectNode',
|
|
347
|
+
{ label: nodeLabel.value },
|
|
348
|
+
))
|
|
345
349
|
const selectionPressedState = computed(() => isPartiallySelected.value ? 'mixed' : isSelected.value)
|
|
346
350
|
const selectionCheckedState = computed(() => isPartiallySelected.value ? 'mixed' : isSelected.value)
|
|
347
351
|
|
|
@@ -689,7 +693,7 @@ onBeforeUnmount(() => {
|
|
|
689
693
|
}
|
|
690
694
|
|
|
691
695
|
.s-tree-node-wrapper--compact .s-tree-node {
|
|
692
|
-
min-height: var(--s-control-hitbox-
|
|
696
|
+
min-height: var(--s-control-hitbox-2xs);
|
|
693
697
|
gap: 0.25rem;
|
|
694
698
|
padding: 0 0.25rem;
|
|
695
699
|
}
|
|
@@ -709,6 +713,10 @@ onBeforeUnmount(() => {
|
|
|
709
713
|
padding-right: 0.78rem;
|
|
710
714
|
}
|
|
711
715
|
|
|
716
|
+
.s-tree-node-wrapper--overlay-actions > .s-tree-node-hitbox .s-tree-node-content-shell {
|
|
717
|
+
position: relative;
|
|
718
|
+
}
|
|
719
|
+
|
|
712
720
|
.s-tree-node-wrapper--overlay-actions .s-tree-node-right {
|
|
713
721
|
position: relative;
|
|
714
722
|
z-index: var(--s-layer-sticky);
|
|
@@ -716,7 +724,7 @@ onBeforeUnmount(() => {
|
|
|
716
724
|
justify-content: flex-end;
|
|
717
725
|
}
|
|
718
726
|
|
|
719
|
-
.s-tree-node-wrapper--overlay-actions .s-tree-node-actions {
|
|
727
|
+
.s-tree-node-wrapper--overlay-actions > .s-tree-node-hitbox .s-tree-node-actions {
|
|
720
728
|
position: absolute;
|
|
721
729
|
top: 50%;
|
|
722
730
|
right: 0.42rem;
|
|
@@ -727,17 +735,22 @@ onBeforeUnmount(() => {
|
|
|
727
735
|
background: color-mix(in srgb, var(--color-surface-0) 98%, transparent);
|
|
728
736
|
box-shadow: -18px 0 24px color-mix(in srgb, var(--color-surface-0) 86%, transparent);
|
|
729
737
|
opacity: 0;
|
|
738
|
+
pointer-events: none;
|
|
730
739
|
transform: translateY(-50%);
|
|
731
740
|
transition: opacity var(--s-motion-fast) ease, transform var(--s-motion-fast) ease;
|
|
732
741
|
}
|
|
733
742
|
|
|
734
|
-
.s-tree-node-wrapper--overlay-actions .s-tree-node-actions--has-persistent {
|
|
743
|
+
.s-tree-node-wrapper--overlay-actions > .s-tree-node-hitbox .s-tree-node-actions--has-persistent {
|
|
735
744
|
opacity: 1;
|
|
745
|
+
pointer-events: auto;
|
|
736
746
|
}
|
|
737
747
|
|
|
738
|
-
.s-tree-node-wrapper--overlay-actions .s-tree-node:hover .s-tree-node-actions,
|
|
739
|
-
.s-tree-node-wrapper--overlay-actions .s-tree-node:focus-within .s-tree-node-actions
|
|
748
|
+
.s-tree-node-wrapper--overlay-actions > .s-tree-node-hitbox:hover > .s-tree-node-row > .s-tree-node-content-shell > .s-tree-node-actions,
|
|
749
|
+
.s-tree-node-wrapper--overlay-actions > .s-tree-node-hitbox:focus-within > .s-tree-node-row > .s-tree-node-content-shell > .s-tree-node-actions,
|
|
750
|
+
.s-tree-node-wrapper--overlay-actions > .s-tree-node-hitbox:hover .s-tree-node > .s-tree-node-right > .s-tree-node-actions,
|
|
751
|
+
.s-tree-node-wrapper--overlay-actions > .s-tree-node-hitbox:focus-within .s-tree-node > .s-tree-node-right > .s-tree-node-actions {
|
|
740
752
|
opacity: 1;
|
|
753
|
+
pointer-events: auto;
|
|
741
754
|
transform: translate(-0.04rem, -50%);
|
|
742
755
|
}
|
|
743
756
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type DialogSurfaceModalSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'wide' | 'full'
|
|
2
2
|
export type DialogSurfaceDrawerSize = 'sm' | 'md' | 'lg'
|
|
3
|
+
export type DialogSurfaceDrawerPlacement = 'right' | 'bottom'
|
|
3
4
|
export type DialogSurfaceTone = 'default' | 'subtle'
|
|
4
5
|
export type DialogSurfaceScrollBehavior = 'page' | 'body'
|
|
5
6
|
|
|
@@ -14,6 +15,7 @@ export interface ModalDialogSurfacePresentation {
|
|
|
14
15
|
export interface DrawerDialogSurfacePresentation {
|
|
15
16
|
kind: 'drawer'
|
|
16
17
|
size: DialogSurfaceDrawerSize
|
|
18
|
+
placement: DialogSurfaceDrawerPlacement
|
|
17
19
|
mobileFullScreen: boolean
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -38,9 +40,10 @@ export class DialogSurfaceContractError extends Error {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
const modalKeys = new Set(['kind', 'size', 'fullscreen', 'surface', 'scrollBehavior'])
|
|
41
|
-
const drawerKeys = new Set(['kind', 'size', 'mobileFullScreen'])
|
|
43
|
+
const drawerKeys = new Set(['kind', 'size', 'placement', 'mobileFullScreen'])
|
|
42
44
|
const modalSizes = new Set<DialogSurfaceModalSize>(['sm', 'md', 'lg', 'xl', '2xl', '3xl', 'wide', 'full'])
|
|
43
45
|
const drawerSizes = new Set<DialogSurfaceDrawerSize>(['sm', 'md', 'lg'])
|
|
46
|
+
const drawerPlacements = new Set<DialogSurfaceDrawerPlacement>(['right', 'bottom'])
|
|
44
47
|
const surfaceTones = new Set<DialogSurfaceTone>(['default', 'subtle'])
|
|
45
48
|
const scrollBehaviors = new Set<DialogSurfaceScrollBehavior>(['page', 'body'])
|
|
46
49
|
|
|
@@ -92,6 +95,9 @@ export function assertDialogSurfacePresentation(
|
|
|
92
95
|
if (!drawerSizes.has(candidate.size as DialogSurfaceDrawerSize)) {
|
|
93
96
|
throw new DialogSurfaceContractError('invalid-size', `Недопустимый размер SDrawer: ${String(candidate.size)}`)
|
|
94
97
|
}
|
|
98
|
+
if (!drawerPlacements.has(candidate.placement as DialogSurfaceDrawerPlacement)) {
|
|
99
|
+
throw new DialogSurfaceContractError('invalid-presentation', `Недопустимое размещение SDrawer: ${String(candidate.placement)}`)
|
|
100
|
+
}
|
|
95
101
|
if (typeof candidate.mobileFullScreen !== 'boolean') {
|
|
96
102
|
throw new DialogSurfaceContractError('invalid-presentation', 'SDrawer presentation требует boolean mobileFullScreen')
|
|
97
103
|
}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
<script setup lang="ts">
|
|
27
27
|
import { computed, type VNodeChild } from 'vue'
|
|
28
|
+
import { useUiKitLocale } from '../../../composables/useUiKitLocale'
|
|
28
29
|
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
29
30
|
import { validateBoundedInteger, validateNonEmptyString } from '../../../internal/runtimeContract'
|
|
30
31
|
import SStack from '../../layout/SStack.vue'
|
|
@@ -50,11 +51,12 @@ export interface Props {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
const props = withDefaults(defineProps<Props>(), {
|
|
53
|
-
emptyText:
|
|
54
|
+
emptyText: undefined,
|
|
54
55
|
defaultOpen: true,
|
|
55
56
|
open: undefined,
|
|
56
57
|
density: 'compact',
|
|
57
58
|
})
|
|
59
|
+
const { t } = useUiKitLocale()
|
|
58
60
|
|
|
59
61
|
defineEmits<{
|
|
60
62
|
(e: 'update:open', value: boolean): void
|
|
@@ -75,7 +77,7 @@ const validatedCount = computed(() => validateBoundedInteger(
|
|
|
75
77
|
const validatedEmptyText = computed(() => validateNonEmptyString(
|
|
76
78
|
'SKanbanLane',
|
|
77
79
|
'emptyText',
|
|
78
|
-
props.emptyText,
|
|
80
|
+
props.emptyText ?? t('kanban.empty'),
|
|
79
81
|
))
|
|
80
82
|
|
|
81
83
|
const ownedAttrs = useOwnedAttrs({ component: 'SKanbanLane', owner: 'collapsible lane composition' })
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
>
|
|
12
12
|
<div v-if="editing && draggable" class="s-cloud-chrome-row__drag-handle">
|
|
13
13
|
<SDragHandle
|
|
14
|
+
size="xs"
|
|
14
15
|
:label="label"
|
|
15
16
|
:can-move-previous="canMoveUp"
|
|
16
17
|
:can-move-next="canMoveDown"
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
<slot name="actions" />
|
|
28
29
|
<SButton
|
|
29
30
|
v-if="editing && canHide && !hidden"
|
|
30
|
-
size="
|
|
31
|
+
size="xs"
|
|
31
32
|
severity="secondary"
|
|
32
33
|
appearance="ghost"
|
|
33
34
|
icon-only
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
/>
|
|
39
40
|
<SButton
|
|
40
41
|
v-if="editing && canRestore"
|
|
41
|
-
size="
|
|
42
|
+
size="xs"
|
|
42
43
|
severity="secondary"
|
|
43
44
|
appearance="ghost"
|
|
44
45
|
icon-only
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
@apply absolute inset-0 flex items-stretch justify-end;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
.s-drawer-positioner[data-placement='bottom'] {
|
|
19
|
+
@apply items-end justify-stretch;
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
.s-drawer-backdrop-enter-active,
|
|
19
23
|
.s-drawer-backdrop-leave-active {
|
|
20
24
|
transition: opacity var(--s-motion-standard) ease;
|
|
@@ -38,9 +42,14 @@
|
|
|
38
42
|
transform: translateX(1.5rem);
|
|
39
43
|
}
|
|
40
44
|
|
|
45
|
+
.s-drawer-shell[data-placement='bottom'].s-drawer-panel-enter-from,
|
|
46
|
+
.s-drawer-shell[data-placement='bottom'].s-drawer-panel-leave-to {
|
|
47
|
+
transform: translateY(1.5rem);
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
@media (max-width: 767px) {
|
|
42
|
-
.s-drawer-panel-enter-from,
|
|
43
|
-
.s-drawer-panel-leave-to {
|
|
51
|
+
.s-drawer-shell[data-placement='right'].s-drawer-panel-enter-from,
|
|
52
|
+
.s-drawer-shell[data-placement='right'].s-drawer-panel-leave-to {
|
|
44
53
|
transform: translateY(1.5rem);
|
|
45
54
|
}
|
|
46
55
|
}
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
</transition>
|
|
6
6
|
|
|
7
7
|
<transition name="s-drawer-panel">
|
|
8
|
-
<div v-if="open" class="s-drawer-shell" :style="surfaceStyle">
|
|
9
|
-
<div class="s-drawer-positioner">
|
|
8
|
+
<div v-if="open" class="s-drawer-shell" :data-placement="validatedPlacement" :style="surfaceStyle">
|
|
9
|
+
<div class="s-drawer-positioner" :data-placement="validatedPlacement">
|
|
10
10
|
<SDialogSurface
|
|
11
11
|
v-bind="ownedAttrs.bindings()"
|
|
12
12
|
:presentation="dialogPresentation"
|
|
13
13
|
:title="title"
|
|
14
|
-
:accessible-name="
|
|
14
|
+
:accessible-name="resolvedAriaLabel"
|
|
15
15
|
:closable="closable"
|
|
16
16
|
:set-surface-element="setPanelElement"
|
|
17
17
|
@request-close="closeDrawer"
|
|
@@ -31,11 +31,14 @@
|
|
|
31
31
|
</template>
|
|
32
32
|
|
|
33
33
|
<script setup lang="ts">
|
|
34
|
-
import { computed
|
|
34
|
+
import { computed } from 'vue'
|
|
35
|
+
import { useUiKitLocale } from '../../../composables/useUiKitLocale'
|
|
35
36
|
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
36
37
|
import { useDialogOverlay } from '../../../composables/useDialogOverlay'
|
|
38
|
+
import { validateBoolean, validateExactString } from '../../../internal/runtimeContract'
|
|
37
39
|
import SDialogSurface from '../_internal/SDialogSurface.vue'
|
|
38
40
|
import type {
|
|
41
|
+
DialogSurfaceDrawerPlacement,
|
|
39
42
|
DialogSurfaceDrawerSize,
|
|
40
43
|
DrawerDialogSurfacePresentation,
|
|
41
44
|
} from '../_internal/dialogSurface'
|
|
@@ -43,12 +46,14 @@ import type {
|
|
|
43
46
|
defineOptions({ inheritAttrs: false })
|
|
44
47
|
|
|
45
48
|
export type DrawerSize = DialogSurfaceDrawerSize
|
|
49
|
+
export type DrawerPlacement = DialogSurfaceDrawerPlacement
|
|
46
50
|
|
|
47
51
|
export interface Props {
|
|
48
52
|
open: boolean
|
|
49
53
|
title?: string
|
|
50
54
|
closable?: boolean
|
|
51
55
|
size?: DrawerSize
|
|
56
|
+
placement?: DrawerPlacement
|
|
52
57
|
mobileFullScreen?: boolean
|
|
53
58
|
ariaLabel?: string
|
|
54
59
|
}
|
|
@@ -57,28 +62,62 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
57
62
|
title: undefined,
|
|
58
63
|
closable: true,
|
|
59
64
|
size: 'md',
|
|
65
|
+
placement: 'right',
|
|
60
66
|
mobileFullScreen: true,
|
|
61
|
-
ariaLabel:
|
|
67
|
+
ariaLabel: undefined,
|
|
62
68
|
})
|
|
69
|
+
const { t } = useUiKitLocale()
|
|
63
70
|
|
|
64
71
|
const emit = defineEmits<{
|
|
65
72
|
'update:open': [value: boolean]
|
|
66
73
|
}>()
|
|
67
74
|
|
|
68
75
|
const ownedAttrs = useOwnedAttrs({ component: 'SDrawer', owner: 'drawer dialog surface' })
|
|
76
|
+
const resolvedAriaLabel = computed(() => props.ariaLabel ?? t('dialog.drawer'))
|
|
77
|
+
const validatedPlacement = computed<DrawerPlacement>(() => validateExactString(
|
|
78
|
+
'SDrawer',
|
|
79
|
+
'placement',
|
|
80
|
+
props.placement,
|
|
81
|
+
['right', 'bottom'] as const,
|
|
82
|
+
))
|
|
83
|
+
const validatedSize = computed<DrawerSize>(() => validateExactString(
|
|
84
|
+
'SDrawer',
|
|
85
|
+
'size',
|
|
86
|
+
props.size,
|
|
87
|
+
['sm', 'md', 'lg'] as const,
|
|
88
|
+
))
|
|
89
|
+
const validatedOpen = computed(() => validateBoolean('SDrawer', 'open', props.open))
|
|
90
|
+
const validatedClosable = computed(() => validateBoolean('SDrawer', 'closable', props.closable))
|
|
91
|
+
const validatedMobileFullScreen = computed(() => validateBoolean(
|
|
92
|
+
'SDrawer',
|
|
93
|
+
'mobileFullScreen',
|
|
94
|
+
props.mobileFullScreen,
|
|
95
|
+
))
|
|
69
96
|
const dialogPresentation = computed<DrawerDialogSurfacePresentation>(() => ({
|
|
70
97
|
kind: 'drawer',
|
|
71
|
-
size:
|
|
72
|
-
|
|
98
|
+
size: validatedSize.value,
|
|
99
|
+
placement: validatedPlacement.value,
|
|
100
|
+
mobileFullScreen: validatedMobileFullScreen.value,
|
|
73
101
|
}))
|
|
74
102
|
|
|
103
|
+
function assertInitialDrawerContract(): void {
|
|
104
|
+
void validatedPlacement.value
|
|
105
|
+
void validatedSize.value
|
|
106
|
+
void validatedOpen.value
|
|
107
|
+
void validatedClosable.value
|
|
108
|
+
void validatedMobileFullScreen.value
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Validate before the overlay owner acquires global layer and focus resources.
|
|
112
|
+
assertInitialDrawerContract()
|
|
113
|
+
|
|
75
114
|
function closeDrawer() {
|
|
76
115
|
emit('update:open', false)
|
|
77
116
|
}
|
|
78
117
|
|
|
79
118
|
const { setPanelElement, backdropStyle, surfaceStyle } = useDialogOverlay({
|
|
80
|
-
open:
|
|
81
|
-
closable:
|
|
119
|
+
open: validatedOpen,
|
|
120
|
+
closable: validatedClosable,
|
|
82
121
|
requestClose: closeDrawer,
|
|
83
122
|
})
|
|
84
123
|
</script>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
v-bind="ownedAttrs.bindings()"
|
|
12
12
|
:presentation="dialogPresentation"
|
|
13
13
|
:title="title"
|
|
14
|
-
:accessible-name="
|
|
14
|
+
:accessible-name="resolvedAriaLabel"
|
|
15
15
|
:closable="closable"
|
|
16
16
|
:set-surface-element="setPanelElement"
|
|
17
17
|
@request-close="closeModal"
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
<script setup lang="ts">
|
|
40
40
|
import { computed, toRef } from 'vue'
|
|
41
|
+
import { useUiKitLocale } from '../../../composables/useUiKitLocale'
|
|
41
42
|
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
42
43
|
import { useDialogOverlay } from '../../../composables/useDialogOverlay'
|
|
43
44
|
import SDialogSurface from '../_internal/SDialogSurface.vue'
|
|
@@ -74,8 +75,9 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
74
75
|
layout: 'desktop',
|
|
75
76
|
surface: 'default',
|
|
76
77
|
scrollBehavior: 'page',
|
|
77
|
-
ariaLabel:
|
|
78
|
+
ariaLabel: undefined,
|
|
78
79
|
})
|
|
80
|
+
const { t } = useUiKitLocale()
|
|
79
81
|
|
|
80
82
|
const emit = defineEmits<{
|
|
81
83
|
'update:open': [value: boolean]
|
|
@@ -88,6 +90,7 @@ function closeModal() {
|
|
|
88
90
|
const isFullscreen = computed(() => props.fullScreen || props.layout === 'mobile-fullscreen')
|
|
89
91
|
|
|
90
92
|
const ownedAttrs = useOwnedAttrs({ component: 'SModal', owner: 'modal dialog surface' })
|
|
93
|
+
const resolvedAriaLabel = computed(() => props.ariaLabel ?? t('dialog.modal'))
|
|
91
94
|
const dialogPresentation = computed<ModalDialogSurfacePresentation>(() => ({
|
|
92
95
|
kind: 'modal',
|
|
93
96
|
size: props.size,
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
>
|
|
50
50
|
<SAsyncState :state="state">
|
|
51
51
|
<template v-if="state.status === 'error' && state.endpoint" #error>
|
|
52
|
-
<SMessage severity="danger" title="
|
|
52
|
+
<SMessage severity="danger" :title="t('feedback.serverResponseError')">
|
|
53
53
|
<div class="space-y-1">
|
|
54
54
|
<div>{{ state.message }}</div>
|
|
55
55
|
<div class="text-xs text-danger-700 dark:text-danger-300">
|
|
56
|
-
|
|
56
|
+
{{ t('feedback.request') }} {{ state.endpoint }}
|
|
57
57
|
</div>
|
|
58
58
|
</div>
|
|
59
59
|
</SMessage>
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
import { computed, useSlots, watchEffect, type FunctionalComponent, type HTMLAttributes, type VNodeProps } from 'vue'
|
|
83
83
|
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
84
84
|
import { validateDomId } from '../../../internal/runtimeContract'
|
|
85
|
+
import { useUiKitLocale } from '../../../composables/useUiKitLocale'
|
|
85
86
|
import SAsyncState, { type SAsyncStateContract } from '../feedback/SAsyncState.vue'
|
|
86
87
|
import SMessage from '../data-display/SMessage.vue'
|
|
87
88
|
import SProgressBar from '../data-display/SProgressBar.vue'
|
|
@@ -148,6 +149,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
148
149
|
})
|
|
149
150
|
|
|
150
151
|
const slots = useSlots()
|
|
152
|
+
const { t } = useUiKitLocale()
|
|
151
153
|
const ownedAttrs = useOwnedAttrs({ component: 'SPanel', owner: 'panel surface root' })
|
|
152
154
|
const validatedTitleId = computed(() => {
|
|
153
155
|
if (props.titleId === undefined) return undefined
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
tabindex="-1"
|
|
57
57
|
role="dialog"
|
|
58
58
|
:id="panelId"
|
|
59
|
-
:aria-label="
|
|
59
|
+
:aria-label="resolvedAriaLabel"
|
|
60
60
|
@keydown.tab="handlePanelTab"
|
|
61
61
|
@focusout="handlePanelFocusout"
|
|
62
62
|
>
|
|
@@ -92,6 +92,7 @@ import SButton, {
|
|
|
92
92
|
type SButtonWidth,
|
|
93
93
|
} from '../controls/SButton.vue';
|
|
94
94
|
import { useFloatingPosition, type FloatingPlacement } from '../../../composables/useFloatingPosition';
|
|
95
|
+
import { useUiKitLocale } from '../../../composables/useUiKitLocale';
|
|
95
96
|
import { findRelativeTabTarget, getFocusableElements } from '../../../internal/focusNavigation';
|
|
96
97
|
import { lastItem } from '../../../internal/es2020';
|
|
97
98
|
import { resolveInteractiveElement } from '../../../internal/interactiveElement';
|
|
@@ -141,7 +142,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
141
142
|
align: 'right',
|
|
142
143
|
surface: 'default',
|
|
143
144
|
width: 'trigger',
|
|
144
|
-
ariaLabel:
|
|
145
|
+
ariaLabel: undefined,
|
|
145
146
|
triggerAppearance: 'ghost',
|
|
146
147
|
triggerSize: 'md',
|
|
147
148
|
triggerWidth: 'full',
|
|
@@ -159,6 +160,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
159
160
|
disabled: false,
|
|
160
161
|
open: undefined,
|
|
161
162
|
});
|
|
163
|
+
const { t } = useUiKitLocale();
|
|
164
|
+
const resolvedAriaLabel = computed(() => props.ariaLabel ?? t('dialog.moreActions'));
|
|
162
165
|
const emit = defineEmits<{
|
|
163
166
|
(e: 'update:open', value: boolean): void;
|
|
164
167
|
}>();
|
|
@@ -192,7 +195,7 @@ const layerZIndex = ref('var(--s-layer-root-floating)');
|
|
|
192
195
|
const resolvedTriggerAriaLabel = computed(() =>
|
|
193
196
|
props.triggerAriaLabel ?? (
|
|
194
197
|
props.triggerIconOnly || (!props.triggerLabel && !slots.default)
|
|
195
|
-
?
|
|
198
|
+
? resolvedAriaLabel.value
|
|
196
199
|
: undefined
|
|
197
200
|
),
|
|
198
201
|
);
|
|
@@ -216,6 +219,7 @@ const {
|
|
|
216
219
|
boundaryPadding: () => 8,
|
|
217
220
|
flip: () => true,
|
|
218
221
|
shift: () => true,
|
|
222
|
+
constrainMainAxis: () => true,
|
|
219
223
|
widthMatch: () => props.width === 'trigger' ? 'min' : 'none',
|
|
220
224
|
zIndex: () => layerZIndex.value,
|
|
221
225
|
scrollStrategy: 'reposition',
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<slot v-if="acceptsNoOwnedAttributes" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import { computed, provide } from 'vue'
|
|
7
|
+
|
|
8
|
+
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
9
|
+
import {
|
|
10
|
+
UI_KIT_LOCALE_CONTEXT,
|
|
11
|
+
resolveUiKitLocaleContext,
|
|
12
|
+
type UiKitLocaleContext,
|
|
13
|
+
} from '../../../internal/localeContext'
|
|
14
|
+
import { UI_KIT_LOCALES, type UiKitLocale, type UiKitMessagesOverride } from '../../../locales/locale'
|
|
15
|
+
import { createUiKitTranslate, mergeUiKitMessages, UI_KIT_MESSAGES } from '../../../locales/messages'
|
|
16
|
+
|
|
17
|
+
defineOptions({ inheritAttrs: false })
|
|
18
|
+
|
|
19
|
+
const props = defineProps<{
|
|
20
|
+
/** Locale for this subtree; omission inherits the nearest provider and ultimately defaults to ru. */
|
|
21
|
+
locale?: UiKitLocale
|
|
22
|
+
/** Exact deep partial override for this subtree. Unknown fields fail at runtime. */
|
|
23
|
+
messages?: UiKitMessagesOverride
|
|
24
|
+
}>()
|
|
25
|
+
|
|
26
|
+
defineSlots<{
|
|
27
|
+
default: () => unknown
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const parent = resolveUiKitLocaleContext()
|
|
31
|
+
const ownedAttrs = useOwnedAttrs({ component: 'SUiKitLocaleProvider', owner: 'DOM-less locale boundary' })
|
|
32
|
+
const locale = computed<UiKitLocale>(() => {
|
|
33
|
+
const value = props.locale ?? parent.locale.value
|
|
34
|
+
if (!UI_KIT_LOCALES.includes(value)) {
|
|
35
|
+
throw new TypeError(`SUiKitLocaleProvider: неизвестная locale ${JSON.stringify(value)}.`)
|
|
36
|
+
}
|
|
37
|
+
return value
|
|
38
|
+
})
|
|
39
|
+
const messages = computed(() => {
|
|
40
|
+
const base = props.locale === undefined || props.locale === parent.locale.value
|
|
41
|
+
? parent.messages.value
|
|
42
|
+
: UI_KIT_MESSAGES[locale.value]
|
|
43
|
+
return mergeUiKitMessages(base, props.messages)
|
|
44
|
+
})
|
|
45
|
+
const context: UiKitLocaleContext = Object.freeze({
|
|
46
|
+
locale,
|
|
47
|
+
messages,
|
|
48
|
+
t: createUiKitTranslate(() => messages.value),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
provide(UI_KIT_LOCALE_CONTEXT, context)
|
|
52
|
+
|
|
53
|
+
const acceptsNoOwnedAttributes = computed(() => {
|
|
54
|
+
ownedAttrs.reject('locale provider не создаёт DOM owner / locale provider does not render a DOM owner')
|
|
55
|
+
return true
|
|
56
|
+
})
|
|
57
|
+
</script>
|
|
@@ -28,20 +28,22 @@
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
.s-button[data-size='xs']:not([data-icon-only='true']) {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
block-size: var(--s-control-hitbox-xs);
|
|
32
|
+
min-block-size: var(--s-control-hitbox-xs);
|
|
33
|
+
padding: 0 0.5rem;
|
|
33
34
|
font-size: 0.75rem;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
.s-button[data-density='compact'][data-size='xs']:not([data-icon-only='true']) {
|
|
37
|
-
|
|
38
|
-
min-
|
|
38
|
+
block-size: var(--s-control-hitbox-2xs);
|
|
39
|
+
min-block-size: var(--s-control-hitbox-2xs);
|
|
39
40
|
padding-block: 0;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
.s-button[data-size='sm']:not([data-icon-only='true']) {
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
block-size: var(--s-control-hitbox-sm);
|
|
45
|
+
min-block-size: var(--s-control-hitbox-sm);
|
|
46
|
+
padding: 0 0.625rem;
|
|
45
47
|
font-size: 0.75rem;
|
|
46
48
|
}
|
|
47
49
|
|
|
@@ -57,14 +59,16 @@
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
.s-button[data-size='md']:not([data-icon-only='true']) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
block-size: var(--s-control-hitbox-md);
|
|
63
|
+
min-block-size: var(--s-control-hitbox-md);
|
|
64
|
+
padding: 0 1.25rem;
|
|
62
65
|
font-size: 0.875rem;
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
.s-button[data-size='lg']:not([data-icon-only='true']) {
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
block-size: var(--s-control-hitbox-lg);
|
|
70
|
+
min-block-size: var(--s-control-hitbox-lg);
|
|
71
|
+
padding: 0 1.75rem;
|
|
68
72
|
font-size: 1.125rem;
|
|
69
73
|
}
|
|
70
74
|
|