@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,186 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-bind="ownedAttrs.bindings()"
|
|
4
|
+
class="s-context-menu-boundary"
|
|
5
|
+
:data-layout="boundaryLayout"
|
|
6
|
+
@click="handleBoundaryClick"
|
|
7
|
+
@contextmenu="handleBoundaryContextMenu"
|
|
8
|
+
>
|
|
9
|
+
<slot :trigger-props="triggerProps" />
|
|
10
|
+
<teleport to="body">
|
|
11
|
+
<transition
|
|
12
|
+
enter-active-class="transition ease-out duration-[var(--s-motion-fast)]"
|
|
13
|
+
enter-from-class="transform opacity-0 scale-95"
|
|
14
|
+
enter-to-class="transform opacity-100 scale-100"
|
|
15
|
+
leave-active-class="transition ease-in duration-[var(--s-motion-instant)]"
|
|
16
|
+
leave-from-class="transform opacity-100 scale-100"
|
|
17
|
+
leave-to-class="transform opacity-0 scale-95"
|
|
18
|
+
>
|
|
19
|
+
<SMenuLayer
|
|
20
|
+
v-if="isOpen"
|
|
21
|
+
ref="layerRef"
|
|
22
|
+
kind="context"
|
|
23
|
+
landmark-label="Контекстные действия"
|
|
24
|
+
:position="position"
|
|
25
|
+
:z-index="zIndex"
|
|
26
|
+
>
|
|
27
|
+
<SMenuSurface
|
|
28
|
+
:id="menuId"
|
|
29
|
+
label="Контекстные действия"
|
|
30
|
+
autofocus
|
|
31
|
+
:initial-focus="initialMenuFocus"
|
|
32
|
+
:items="items"
|
|
33
|
+
@dismiss-tree="closeMenu('outside')"
|
|
34
|
+
@escape="closeMenu('escape')"
|
|
35
|
+
@focus-exit="closeMenu('outside')"
|
|
36
|
+
@select="handleSelect"
|
|
37
|
+
@tab="closeMenuForTab"
|
|
38
|
+
/>
|
|
39
|
+
</SMenuLayer>
|
|
40
|
+
</transition>
|
|
41
|
+
</teleport>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
import { computed, ref, useId, watch, watchEffect } from 'vue'
|
|
47
|
+
import type { VNodeChild } from 'vue'
|
|
48
|
+
import { findFocusableOwner } from '../../../internal/focusNavigation'
|
|
49
|
+
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
50
|
+
import { useInteractiveLeafRegistration } from '../../../internal/passiveContentContract'
|
|
51
|
+
import { useMenuLayer } from '../../../internal/useMenuLayer'
|
|
52
|
+
import SMenuLayer from '../_internal/SMenuLayer.vue'
|
|
53
|
+
import type { SMenuLayerApi } from '../_internal/SMenuLayer.vue'
|
|
54
|
+
import SMenuSurface from './SMenuSurface.vue'
|
|
55
|
+
import { validateSMenuEntries, type SMenuActivationEvent, type SMenuEntry, type SMenuSelectableEntry } from './menu'
|
|
56
|
+
|
|
57
|
+
defineOptions({ inheritAttrs: false })
|
|
58
|
+
|
|
59
|
+
export interface Props {
|
|
60
|
+
items: readonly SMenuEntry[];
|
|
61
|
+
/** False оставляет boundary полностью inert: не открывает menu и не перехватывает native events. / False keeps the boundary fully inert: it neither opens the menu nor intercepts native events. */
|
|
62
|
+
enabled?: boolean;
|
|
63
|
+
trigger?: 'contextmenu' | 'click' | 'both';
|
|
64
|
+
boundaryLayout?: 'block' | 'inline' | 'contents';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* ARIA-связь canonical trigger с принадлежащим ему context-menu layer.
|
|
69
|
+
* ARIA relationship between the canonical trigger and its owned context-menu layer.
|
|
70
|
+
*/
|
|
71
|
+
export interface SContextMenuTriggerProps {
|
|
72
|
+
readonly 'aria-haspopup'?: 'menu'
|
|
73
|
+
readonly 'aria-expanded'?: boolean
|
|
74
|
+
readonly 'aria-controls'?: string
|
|
75
|
+
readonly onKeydown?: (event: KeyboardEvent) => void
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
79
|
+
enabled: true,
|
|
80
|
+
trigger: 'contextmenu',
|
|
81
|
+
boundaryLayout: 'block',
|
|
82
|
+
})
|
|
83
|
+
const emit = defineEmits<{
|
|
84
|
+
select: [entry: SMenuSelectableEntry, event: SMenuActivationEvent]
|
|
85
|
+
}>()
|
|
86
|
+
defineSlots<{
|
|
87
|
+
/** Canonical interactive owner получает готовые ARIA props. / The canonical interactive owner receives complete ARIA props. */
|
|
88
|
+
default?: (props: { triggerProps: SContextMenuTriggerProps }) => VNodeChild
|
|
89
|
+
}>()
|
|
90
|
+
const ownedAttrs = useOwnedAttrs({ component: 'SContextMenu', owner: 'interaction boundary root' })
|
|
91
|
+
useInteractiveLeafRegistration({ owner: 'SContextMenu', active: () => props.enabled })
|
|
92
|
+
|
|
93
|
+
const generatedMenuId = useId()
|
|
94
|
+
const menuId = `s-context-menu-${generatedMenuId}`
|
|
95
|
+
const initialMenuFocus = ref<'first' | 'last'>('first')
|
|
96
|
+
const layerRef = ref<SMenuLayerApi | null>(null)
|
|
97
|
+
const layerElement = computed(() => layerRef.value?.getElement() ?? null)
|
|
98
|
+
const {
|
|
99
|
+
isOpen,
|
|
100
|
+
position,
|
|
101
|
+
zIndex,
|
|
102
|
+
open,
|
|
103
|
+
close: closeMenu,
|
|
104
|
+
closeForTab: closeMenuForTab,
|
|
105
|
+
} = useMenuLayer(layerElement)
|
|
106
|
+
const triggerProps = computed<SContextMenuTriggerProps>(() => (
|
|
107
|
+
props.enabled
|
|
108
|
+
? {
|
|
109
|
+
'aria-haspopup': 'menu',
|
|
110
|
+
'aria-expanded': isOpen.value,
|
|
111
|
+
'aria-controls': menuId,
|
|
112
|
+
onKeydown: handleTriggerKeydown,
|
|
113
|
+
}
|
|
114
|
+
: {}
|
|
115
|
+
))
|
|
116
|
+
|
|
117
|
+
const openMenu = async (event: MouseEvent): Promise<void> => {
|
|
118
|
+
if (!props.enabled) return
|
|
119
|
+
initialMenuFocus.value = 'first'
|
|
120
|
+
const boundary = event.currentTarget instanceof Element ? event.currentTarget : null
|
|
121
|
+
const focusTarget = findFocusableOwner(event.target, boundary)
|
|
122
|
+
?? findFocusableOwner(document.activeElement)
|
|
123
|
+
const isKeyboardActivation = event.clientX === 0 && event.clientY === 0 && event.detail === 0
|
|
124
|
+
await open(
|
|
125
|
+
isKeyboardActivation && focusTarget
|
|
126
|
+
? { type: 'element', element: focusTarget, align: 'left' }
|
|
127
|
+
: { type: 'point', x: event.clientX, y: event.clientY },
|
|
128
|
+
focusTarget,
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const handleTriggerKeydown = (event: KeyboardEvent): void => {
|
|
133
|
+
if (
|
|
134
|
+
!props.enabled
|
|
135
|
+
|| (props.trigger !== 'click' && props.trigger !== 'both')
|
|
136
|
+
|| (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')
|
|
137
|
+
) return
|
|
138
|
+
const trigger = event.currentTarget
|
|
139
|
+
if (!(trigger instanceof HTMLElement)) {
|
|
140
|
+
throw new TypeError('SContextMenu: trigger keydown currentTarget must be an HTMLElement')
|
|
141
|
+
}
|
|
142
|
+
event.preventDefault()
|
|
143
|
+
event.stopPropagation()
|
|
144
|
+
initialMenuFocus.value = event.key === 'ArrowUp' ? 'last' : 'first'
|
|
145
|
+
void open({ type: 'element', element: trigger, align: 'left' }, trigger)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const activateBoundary = (event: MouseEvent): void => {
|
|
149
|
+
if (!props.enabled) return
|
|
150
|
+
event.preventDefault()
|
|
151
|
+
event.stopPropagation()
|
|
152
|
+
void openMenu(event)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const handleBoundaryClick = (event: MouseEvent): void => {
|
|
156
|
+
if (props.trigger === 'click' || props.trigger === 'both') activateBoundary(event)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const handleBoundaryContextMenu = (event: MouseEvent): void => {
|
|
160
|
+
if (props.trigger === 'contextmenu' || props.trigger === 'both') activateBoundary(event)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
watch(() => props.enabled, (enabled) => {
|
|
164
|
+
if (!enabled) closeMenu('programmatic')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const handleSelect = (item: SMenuSelectableEntry, event: SMenuActivationEvent): void => {
|
|
168
|
+
if (item.disabled) return
|
|
169
|
+
closeMenu('selection')
|
|
170
|
+
emit('select', item, event)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
watchEffect(() => {
|
|
174
|
+
validateSMenuEntries(props.items)
|
|
175
|
+
if (typeof props.enabled !== 'boolean') throw new TypeError('SContextMenu: enabled must be a boolean')
|
|
176
|
+
if (!['contextmenu', 'click', 'both'].includes(props.trigger)) {
|
|
177
|
+
throw new TypeError("SContextMenu: trigger must be 'contextmenu', 'click', or 'both'")
|
|
178
|
+
}
|
|
179
|
+
if (!['block', 'inline', 'contents'].includes(props.boundaryLayout)) {
|
|
180
|
+
throw new TypeError("SContextMenu: boundaryLayout must be 'block', 'inline', or 'contents'")
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
</script>
|
|
185
|
+
|
|
186
|
+
<style lang="postcss" src="./SContextMenu.css" scoped></style>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<SButton
|
|
3
|
+
v-bind="ownedAttrs.bindings()"
|
|
4
|
+
appearance="text"
|
|
5
|
+
icon-only
|
|
6
|
+
shape="circle"
|
|
7
|
+
:size="size"
|
|
8
|
+
:severity="partial ? 'warn' : inContext ? 'primary' : 'secondary'"
|
|
9
|
+
:title="accessibleTitle"
|
|
10
|
+
:aria-label="accessibleTitle"
|
|
11
|
+
:pressed="pressedState"
|
|
12
|
+
:leading-icon="stateIcon"
|
|
13
|
+
:loading="loading"
|
|
14
|
+
@click="emit('click')"
|
|
15
|
+
/>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import { computed, type Component } from 'vue'
|
|
20
|
+
import { Minus, MinusCircle, PlusCircle } from '../../icons/sputnigUiIcons'
|
|
21
|
+
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
22
|
+
import { validateNonEmptyString } from '../../../internal/runtimeContract'
|
|
23
|
+
import SButton from './SButton.vue'
|
|
24
|
+
|
|
25
|
+
defineOptions({ inheritAttrs: false })
|
|
26
|
+
|
|
27
|
+
/** Доменный toggle добавления сущности в контекст. / Domain toggle for adding an entity to context. */
|
|
28
|
+
export interface Props {
|
|
29
|
+
inContext: boolean
|
|
30
|
+
partial?: boolean
|
|
31
|
+
addTitle?: string
|
|
32
|
+
removeTitle?: string
|
|
33
|
+
size?: 'sm' | 'md' | 'lg'
|
|
34
|
+
loading?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
38
|
+
partial: false,
|
|
39
|
+
addTitle: 'Добавить в контекст',
|
|
40
|
+
removeTitle: 'Убрать из контекста',
|
|
41
|
+
size: 'sm',
|
|
42
|
+
loading: false,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const emit = defineEmits<{
|
|
46
|
+
(e: 'click'): void
|
|
47
|
+
}>()
|
|
48
|
+
const ownedAttrs = useOwnedAttrs({ component: 'SContextToggleButton', owner: 'toggle button' })
|
|
49
|
+
|
|
50
|
+
const accessibleLabels = computed(() => ({
|
|
51
|
+
add: validateNonEmptyString('SContextToggleButton', 'addTitle', props.addTitle),
|
|
52
|
+
remove: validateNonEmptyString('SContextToggleButton', 'removeTitle', props.removeTitle),
|
|
53
|
+
}))
|
|
54
|
+
const accessibleTitle = computed(() => (
|
|
55
|
+
props.inContext ? accessibleLabels.value.remove : accessibleLabels.value.add
|
|
56
|
+
))
|
|
57
|
+
const pressedState = computed<boolean | 'mixed'>(() => (
|
|
58
|
+
props.partial ? 'mixed' : props.inContext
|
|
59
|
+
))
|
|
60
|
+
const stateIcon = computed<Component>(() => {
|
|
61
|
+
if (!props.inContext) return PlusCircle
|
|
62
|
+
return props.partial ? Minus : MinusCircle
|
|
63
|
+
})
|
|
64
|
+
</script>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<SButton
|
|
3
|
+
v-bind="ownedAttrs.bindings()"
|
|
4
|
+
size="sm"
|
|
5
|
+
severity="secondary"
|
|
6
|
+
appearance="ghost"
|
|
7
|
+
icon-only
|
|
8
|
+
:leading-icon="DragIcon"
|
|
9
|
+
:disabled="disabled"
|
|
10
|
+
:pressed="keyboardActive"
|
|
11
|
+
:title="accessibleLabel"
|
|
12
|
+
:aria-label="accessibleLabel"
|
|
13
|
+
:aria-keyshortcuts="keyboardShortcuts"
|
|
14
|
+
data-s-drag-handle
|
|
15
|
+
:data-state="keyboardActive ? 'keyboard-active' : 'idle'"
|
|
16
|
+
@click="handleClick"
|
|
17
|
+
@pointerdown="handlePointerDown"
|
|
18
|
+
@keydown="handleKeydown"
|
|
19
|
+
/>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { computed, ref, watchEffect } from 'vue'
|
|
24
|
+
import { getSputnigUiIconComponent } from '../../icons/sputnigUiIcons'
|
|
25
|
+
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
26
|
+
import { validateNonEmptyString } from '../../../internal/runtimeContract'
|
|
27
|
+
import SButton from './SButton.vue'
|
|
28
|
+
|
|
29
|
+
defineOptions({ inheritAttrs: false })
|
|
30
|
+
|
|
31
|
+
const props = withDefaults(defineProps<{
|
|
32
|
+
label: string
|
|
33
|
+
orientation?: 'vertical' | 'horizontal'
|
|
34
|
+
canMovePrevious?: boolean
|
|
35
|
+
canMoveNext?: boolean
|
|
36
|
+
disabled?: boolean
|
|
37
|
+
}>(), {
|
|
38
|
+
orientation: 'vertical',
|
|
39
|
+
canMovePrevious: false,
|
|
40
|
+
canMoveNext: false,
|
|
41
|
+
disabled: false,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const emit = defineEmits<{
|
|
45
|
+
(event: 'drag-start', pointerEvent: PointerEvent): void
|
|
46
|
+
(event: 'reorder', direction: -1 | 1): void
|
|
47
|
+
(event: 'keyboard-active-change', active: boolean): void
|
|
48
|
+
}>()
|
|
49
|
+
|
|
50
|
+
const DragIcon = getSputnigUiIconComponent('drag')
|
|
51
|
+
const ownedAttrs = useOwnedAttrs({ component: 'SDragHandle', owner: 'drag handle command' })
|
|
52
|
+
const keyboardActive = ref(false)
|
|
53
|
+
const validatedLabel = computed(() => validateNonEmptyString('SDragHandle', 'label', props.label))
|
|
54
|
+
const keyboardShortcuts = computed(() => props.orientation === 'vertical'
|
|
55
|
+
? 'Enter Space ArrowUp ArrowDown Escape'
|
|
56
|
+
: 'Enter Space ArrowLeft ArrowRight Escape')
|
|
57
|
+
const accessibleLabel = computed(() => keyboardActive.value
|
|
58
|
+
? `Перемещение: ${validatedLabel.value}. Используйте стрелки, Enter — завершить, Escape — отменить`
|
|
59
|
+
: `Переместить: ${validatedLabel.value}. Нажмите Enter и используйте стрелки`)
|
|
60
|
+
|
|
61
|
+
function setKeyboardActive(active: boolean): void {
|
|
62
|
+
if (keyboardActive.value === active) return
|
|
63
|
+
keyboardActive.value = active
|
|
64
|
+
emit('keyboard-active-change', active)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function handleClick(event: MouseEvent): void {
|
|
68
|
+
if (props.disabled || event.detail > 0) return
|
|
69
|
+
setKeyboardActive(!keyboardActive.value)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function handlePointerDown(event: PointerEvent): void {
|
|
73
|
+
if (props.disabled) return
|
|
74
|
+
setKeyboardActive(false)
|
|
75
|
+
emit('drag-start', event)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function handleKeydown(event: KeyboardEvent): void {
|
|
79
|
+
if (props.disabled || !keyboardActive.value) return
|
|
80
|
+
if (event.key === 'Escape') {
|
|
81
|
+
event.preventDefault()
|
|
82
|
+
setKeyboardActive(false)
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const previousKey = props.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'
|
|
87
|
+
const nextKey = props.orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight'
|
|
88
|
+
if (event.key !== previousKey && event.key !== nextKey) return
|
|
89
|
+
|
|
90
|
+
event.preventDefault()
|
|
91
|
+
const direction = event.key === previousKey ? -1 : 1
|
|
92
|
+
if (direction === -1 && !props.canMovePrevious) return
|
|
93
|
+
if (direction === 1 && !props.canMoveNext) return
|
|
94
|
+
emit('reorder', direction)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
watchEffect(() => {
|
|
98
|
+
if (props.disabled) setKeyboardActive(false)
|
|
99
|
+
})
|
|
100
|
+
</script>
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="s-dropdown-menu">
|
|
3
|
+
<slot v-if="hasCustomTrigger" :trigger-attrs="triggerAttrs()" />
|
|
4
|
+
<SButton
|
|
5
|
+
v-else
|
|
6
|
+
v-bind="triggerAttrs()"
|
|
7
|
+
label="Опции"
|
|
8
|
+
:disclosure="{ expanded: isOpen, controls: menuId }"
|
|
9
|
+
severity="secondary"
|
|
10
|
+
/>
|
|
11
|
+
<teleport to="body">
|
|
12
|
+
<SMenuLayer
|
|
13
|
+
v-if="isOpen"
|
|
14
|
+
ref="layerRef"
|
|
15
|
+
kind="dropdown"
|
|
16
|
+
:landmark-label="menuLabel"
|
|
17
|
+
:position="position"
|
|
18
|
+
:z-index="zIndex"
|
|
19
|
+
>
|
|
20
|
+
<SMenuSurface
|
|
21
|
+
ref="menuSurfaceRef"
|
|
22
|
+
:id="menuId"
|
|
23
|
+
:label="menuLabel"
|
|
24
|
+
density="compact"
|
|
25
|
+
autofocus
|
|
26
|
+
:initial-focus="initialFocus"
|
|
27
|
+
:items="items"
|
|
28
|
+
@dismiss-tree="close('outside')"
|
|
29
|
+
@escape="close('escape')"
|
|
30
|
+
@focus-exit="close('outside')"
|
|
31
|
+
@select="select"
|
|
32
|
+
@tab="closeForTab"
|
|
33
|
+
/>
|
|
34
|
+
</SMenuLayer>
|
|
35
|
+
</teleport>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script setup lang="ts">
|
|
40
|
+
import {
|
|
41
|
+
computed,
|
|
42
|
+
nextTick,
|
|
43
|
+
onMounted,
|
|
44
|
+
ref,
|
|
45
|
+
useId,
|
|
46
|
+
watchEffect,
|
|
47
|
+
type VNodeChild,
|
|
48
|
+
type VNodeRef,
|
|
49
|
+
} from 'vue'
|
|
50
|
+
import { useOwnedAttrs, type OwnedAttributeBindings } from '../../../internal/ownedAttrs'
|
|
51
|
+
import { useMenuLayer } from '../../../internal/useMenuLayer'
|
|
52
|
+
import SMenuLayer from '../_internal/SMenuLayer.vue'
|
|
53
|
+
import type { SMenuLayerApi } from '../_internal/SMenuLayer.vue'
|
|
54
|
+
import SButton from './SButton.vue'
|
|
55
|
+
import SMenuSurface, { type SMenuSurfaceApi } from './SMenuSurface.vue'
|
|
56
|
+
import { validateSMenuEntries, type SMenuActivationEvent, type SMenuEntry, type SMenuSelectableEntry } from './menu'
|
|
57
|
+
|
|
58
|
+
defineOptions({ inheritAttrs: false })
|
|
59
|
+
|
|
60
|
+
const props = withDefaults(defineProps<{
|
|
61
|
+
items: readonly SMenuEntry[]
|
|
62
|
+
align?: 'left' | 'right'
|
|
63
|
+
menuLabel?: string
|
|
64
|
+
}>(), {
|
|
65
|
+
align: 'right',
|
|
66
|
+
menuLabel: 'Дополнительные действия',
|
|
67
|
+
})
|
|
68
|
+
const emit = defineEmits<{
|
|
69
|
+
select: [entry: SMenuSelectableEntry, event: SMenuActivationEvent]
|
|
70
|
+
}>()
|
|
71
|
+
export type SDropdownMenuTriggerAttrs = OwnedAttributeBindings<'SDropdownMenu'> & {
|
|
72
|
+
readonly ref: VNodeRef
|
|
73
|
+
readonly 'aria-haspopup': 'menu'
|
|
74
|
+
readonly 'aria-expanded': boolean
|
|
75
|
+
readonly 'aria-controls': string
|
|
76
|
+
readonly onClick: (event?: Event) => void
|
|
77
|
+
readonly onKeydown: (event: KeyboardEvent) => void
|
|
78
|
+
}
|
|
79
|
+
const slots = defineSlots<{
|
|
80
|
+
/** Единственный custom-trigger contract: attrs должны принадлежать реальному control. / The sole custom-trigger contract: attrs must be bound to the actual control. */
|
|
81
|
+
default?: (props: { triggerAttrs: SDropdownMenuTriggerAttrs }) => VNodeChild
|
|
82
|
+
}>()
|
|
83
|
+
|
|
84
|
+
const ownedAttrs = useOwnedAttrs({ component: 'SDropdownMenu', owner: 'menu trigger' })
|
|
85
|
+
const layerRef = ref<SMenuLayerApi | null>(null)
|
|
86
|
+
const menuSurfaceRef = ref<SMenuSurfaceApi | null>(null)
|
|
87
|
+
let registeredTriggerTarget: Element | object | null = null
|
|
88
|
+
const layerElement = computed(() => layerRef.value?.getElement() ?? null)
|
|
89
|
+
const {
|
|
90
|
+
isOpen,
|
|
91
|
+
position,
|
|
92
|
+
zIndex,
|
|
93
|
+
close,
|
|
94
|
+
closeForTab,
|
|
95
|
+
open,
|
|
96
|
+
toggle: toggleLayer,
|
|
97
|
+
} = useMenuLayer(layerElement)
|
|
98
|
+
const menuId = `s-dropdown-menu-${useId()}`
|
|
99
|
+
const initialFocus = ref<'first' | 'last'>('first')
|
|
100
|
+
const hasCustomTrigger = Boolean(slots.default)
|
|
101
|
+
|
|
102
|
+
function resolveTriggerElement(target: Element | object | null): HTMLElement | null {
|
|
103
|
+
if (target === null) return null
|
|
104
|
+
if (target instanceof HTMLElement) return target
|
|
105
|
+
const getElement = Reflect.get(target, 'getElement')
|
|
106
|
+
if (typeof getElement !== 'function') {
|
|
107
|
+
throw new Error('SDropdownMenu: trigger ref must resolve to an HTMLElement or UI-kit leaf API')
|
|
108
|
+
}
|
|
109
|
+
const element: unknown = Reflect.apply(getElement, target, [])
|
|
110
|
+
if (!(element instanceof HTMLElement)) {
|
|
111
|
+
throw new Error('SDropdownMenu: trigger leaf getElement() must return an HTMLElement')
|
|
112
|
+
}
|
|
113
|
+
return element
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const bindTriggerElement: VNodeRef = (target) => {
|
|
117
|
+
if (target !== null && registeredTriggerTarget !== null && target !== registeredTriggerTarget) {
|
|
118
|
+
throw new Error('SDropdownMenu: trigger binding must belong to exactly one UI-kit leaf')
|
|
119
|
+
}
|
|
120
|
+
registeredTriggerTarget = target
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const triggerAttrs = (): SDropdownMenuTriggerAttrs => {
|
|
124
|
+
const attrs = ownedAttrs.bindings()
|
|
125
|
+
if (Object.prototype.hasOwnProperty.call(attrs, 'onClick')) {
|
|
126
|
+
throw new Error('SDropdownMenu: @click belongs to the typed triggerAttrs activation contract')
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
...attrs,
|
|
130
|
+
ref: bindTriggerElement,
|
|
131
|
+
'aria-haspopup': 'menu' as const,
|
|
132
|
+
'aria-expanded': isOpen.value,
|
|
133
|
+
'aria-controls': menuId,
|
|
134
|
+
onClick: toggle,
|
|
135
|
+
onKeydown: handleTriggerKeydown,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function triggerElement(event?: Event): HTMLElement {
|
|
140
|
+
const trigger = resolveTriggerElement(registeredTriggerTarget)
|
|
141
|
+
if (!trigger) throw new Error('SDropdownMenu: expected exactly one trigger, received 0')
|
|
142
|
+
if (event?.currentTarget instanceof HTMLElement && event.currentTarget !== trigger) {
|
|
143
|
+
throw new Error('SDropdownMenu: activation target does not match the registered trigger')
|
|
144
|
+
}
|
|
145
|
+
return trigger
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
onMounted(() => {
|
|
149
|
+
const trigger = triggerElement()
|
|
150
|
+
if (trigger.getAttribute('aria-haspopup') !== 'menu' || trigger.getAttribute('aria-controls') !== menuId) {
|
|
151
|
+
throw new Error('SDropdownMenu: expected exactly one trigger, received 0')
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
function toggle(event?: Event): void {
|
|
156
|
+
const trigger = triggerElement(event)
|
|
157
|
+
initialFocus.value = 'first'
|
|
158
|
+
toggleLayer({ type: 'element', element: trigger, align: props.align }, trigger)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function handleTriggerKeydown(event: KeyboardEvent): Promise<void> {
|
|
162
|
+
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return
|
|
163
|
+
event.preventDefault()
|
|
164
|
+
event.stopPropagation()
|
|
165
|
+
const trigger = triggerElement(event)
|
|
166
|
+
initialFocus.value = event.key === 'ArrowUp' ? 'last' : 'first'
|
|
167
|
+
if (!isOpen.value) {
|
|
168
|
+
await open({ type: 'element', element: trigger, align: props.align }, trigger)
|
|
169
|
+
}
|
|
170
|
+
await nextTick()
|
|
171
|
+
if (initialFocus.value === 'last') menuSurfaceRef.value?.focusLast()
|
|
172
|
+
else menuSurfaceRef.value?.focusFirst()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function select(item: SMenuSelectableEntry, event: SMenuActivationEvent): void {
|
|
176
|
+
if (item.disabled) return
|
|
177
|
+
close('selection')
|
|
178
|
+
emit('select', item, event)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
watchEffect(() => {
|
|
182
|
+
validateSMenuEntries(props.items)
|
|
183
|
+
if (!props.menuLabel.trim()) throw new TypeError('SDropdownMenu: menuLabel must be a non-empty string')
|
|
184
|
+
if (props.align !== 'left' && props.align !== 'right') throw new TypeError("SDropdownMenu: align must be 'left' or 'right'")
|
|
185
|
+
})
|
|
186
|
+
</script>
|
|
187
|
+
|
|
188
|
+
<style lang="postcss" src="./SDropdownMenu.css" scoped></style>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
@reference "../../../styles/reference.css";
|
|
2
|
+
|
|
3
|
+
.s-field {
|
|
4
|
+
@apply grid w-full min-w-0;
|
|
5
|
+
grid-template-areas:
|
|
6
|
+
"label"
|
|
7
|
+
"control"
|
|
8
|
+
"help"
|
|
9
|
+
"error";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.s-field[data-fill='container'] {
|
|
13
|
+
@apply h-full;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.s-field--stacked[data-fill='container'] {
|
|
17
|
+
grid-template-rows: auto minmax(0, 1fr) auto auto;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.s-field--inline[data-fill='container'] {
|
|
21
|
+
grid-template-rows: minmax(0, 1fr) auto auto;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.s-field[data-fill='container'] .s-field__control {
|
|
25
|
+
@apply min-h-0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.s-field--inline {
|
|
29
|
+
@apply items-start gap-x-2;
|
|
30
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
31
|
+
grid-template-areas:
|
|
32
|
+
"control label"
|
|
33
|
+
". help"
|
|
34
|
+
". error";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.s-field__label {
|
|
38
|
+
grid-area: label;
|
|
39
|
+
@apply mb-1 block min-w-0 text-sm font-medium text-surface-700 dark:text-surface-300;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.s-field--inline .s-field__label {
|
|
43
|
+
@apply mb-0 cursor-pointer whitespace-normal;
|
|
44
|
+
overflow-wrap: anywhere;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.s-field__control {
|
|
48
|
+
grid-area: control;
|
|
49
|
+
@apply min-w-0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.s-field--inline .s-field__control {
|
|
53
|
+
@apply flex items-center;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.s-field__required {
|
|
57
|
+
@apply ml-0.5 text-danger-600 dark:text-danger-400;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.s-field__help,
|
|
61
|
+
.s-field__error {
|
|
62
|
+
@apply mt-1 min-w-0 whitespace-normal text-xs;
|
|
63
|
+
overflow-wrap: anywhere;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.s-field__help {
|
|
67
|
+
grid-area: help;
|
|
68
|
+
@apply text-surface-500 dark:text-surface-400;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.s-field__error {
|
|
72
|
+
grid-area: error;
|
|
73
|
+
@apply text-danger-600 dark:text-danger-400;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.s-field[data-disabled='true'] .s-field__label,
|
|
77
|
+
.s-field[data-readonly='true'] .s-field__label {
|
|
78
|
+
color: var(--s-field-disabled-foreground);
|
|
79
|
+
}
|