@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,134 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<SFieldGroup
|
|
3
|
+
v-bind="ownedAttrs.bindings()"
|
|
4
|
+
:id="groupId"
|
|
5
|
+
:label="label"
|
|
6
|
+
:help-text="helpText"
|
|
7
|
+
:error-message="errorMessage"
|
|
8
|
+
:required="required"
|
|
9
|
+
:invalid="invalid"
|
|
10
|
+
:disabled="disabled"
|
|
11
|
+
:readonly="readonly"
|
|
12
|
+
:layout="layout"
|
|
13
|
+
:columns="columns"
|
|
14
|
+
>
|
|
15
|
+
<template #default="group">
|
|
16
|
+
<SCheckbox
|
|
17
|
+
v-for="record in validatedOptions"
|
|
18
|
+
:id="record.id"
|
|
19
|
+
:key="record.token"
|
|
20
|
+
:model-value="selectedTokens.has(record.token)"
|
|
21
|
+
:label="record.option.label"
|
|
22
|
+
:help-text="record.option.helpText"
|
|
23
|
+
:disabled="group.disabled || record.option.disabled"
|
|
24
|
+
:readonly="group.readonly"
|
|
25
|
+
:invalid="group.invalid"
|
|
26
|
+
:aria-describedby="group.describedBy"
|
|
27
|
+
@update:model-value="updateOption(record.option.value, $event)"
|
|
28
|
+
/>
|
|
29
|
+
</template>
|
|
30
|
+
</SFieldGroup>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script lang="ts">
|
|
34
|
+
import type { SelectionKey as PublicSelectionKey } from '../_internal/useSelectionRoving'
|
|
35
|
+
|
|
36
|
+
/** Одна checkbox option со стабильной domain identity. / One checkbox option with stable domain identity. */
|
|
37
|
+
export interface SCheckboxGroupOption<T extends PublicSelectionKey = PublicSelectionKey> {
|
|
38
|
+
value: T
|
|
39
|
+
label: string
|
|
40
|
+
helpText?: string
|
|
41
|
+
disabled?: boolean
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<script setup lang="ts" generic="T extends SelectionKey = string">
|
|
46
|
+
import { computed, useId } from 'vue'
|
|
47
|
+
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
48
|
+
import { validateDomId, validateNonEmptyString } from '../../../internal/runtimeContract'
|
|
49
|
+
import {
|
|
50
|
+
selectionKeyToken,
|
|
51
|
+
validateSelectionIdentities,
|
|
52
|
+
type SelectionKey,
|
|
53
|
+
} from '../_internal/useSelectionRoving'
|
|
54
|
+
import SCheckbox from './SCheckbox.vue'
|
|
55
|
+
import SFieldGroup from './SFieldGroup.vue'
|
|
56
|
+
import type { SFieldGroupColumns, SFieldGroupLayout } from './SFieldGroup.vue'
|
|
57
|
+
|
|
58
|
+
defineOptions({ inheritAttrs: false })
|
|
59
|
+
|
|
60
|
+
const props = withDefaults(defineProps<{
|
|
61
|
+
modelValue: readonly T[]
|
|
62
|
+
options: readonly SCheckboxGroupOption<T>[]
|
|
63
|
+
label: string
|
|
64
|
+
id?: string
|
|
65
|
+
helpText?: string
|
|
66
|
+
errorMessage?: string
|
|
67
|
+
required?: boolean
|
|
68
|
+
invalid?: boolean
|
|
69
|
+
disabled?: boolean
|
|
70
|
+
readonly?: boolean
|
|
71
|
+
layout?: SFieldGroupLayout
|
|
72
|
+
columns?: SFieldGroupColumns
|
|
73
|
+
}>(), {
|
|
74
|
+
id: undefined,
|
|
75
|
+
helpText: undefined,
|
|
76
|
+
errorMessage: undefined,
|
|
77
|
+
required: false,
|
|
78
|
+
invalid: false,
|
|
79
|
+
disabled: false,
|
|
80
|
+
readonly: false,
|
|
81
|
+
layout: 'stacked',
|
|
82
|
+
columns: 1,
|
|
83
|
+
})
|
|
84
|
+
const emit = defineEmits<{ 'update:modelValue': [value: readonly T[]] }>()
|
|
85
|
+
const ownedAttrs = useOwnedAttrs({ component: 'SCheckboxGroup', owner: 'field group' })
|
|
86
|
+
const generatedId = useId()
|
|
87
|
+
const groupId = computed(() => validateDomId(
|
|
88
|
+
'SCheckboxGroup',
|
|
89
|
+
'id',
|
|
90
|
+
props.id ?? `s-checkbox-group-${generatedId}`,
|
|
91
|
+
))
|
|
92
|
+
|
|
93
|
+
const validatedOptions = computed(() => {
|
|
94
|
+
validateSelectionIdentities('SCheckboxGroup', props.options.map((option, index) => ({
|
|
95
|
+
key: option.value,
|
|
96
|
+
coordinate: `options[${index}].value`,
|
|
97
|
+
})))
|
|
98
|
+
return props.options.map((option, index) => {
|
|
99
|
+
validateNonEmptyString('SCheckboxGroup', `options[${index}].label`, option.label)
|
|
100
|
+
const token = selectionKeyToken('SCheckboxGroup', option.value, `options[${index}].value`)
|
|
101
|
+
return { option, token, id: `${groupId.value}-option-${token}` }
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const optionsByToken = computed<ReadonlyMap<string, T>>(() => new Map(
|
|
106
|
+
validatedOptions.value.map((record) => [record.token, record.option.value]),
|
|
107
|
+
))
|
|
108
|
+
|
|
109
|
+
const selectedTokens = computed(() => {
|
|
110
|
+
const modelIndex = validateSelectionIdentities('SCheckboxGroup', props.modelValue.map((value, index) => ({
|
|
111
|
+
key: value,
|
|
112
|
+
coordinate: `modelValue[${index}]`,
|
|
113
|
+
})))
|
|
114
|
+
const optionIndex = optionsByToken.value
|
|
115
|
+
for (const [token] of modelIndex) {
|
|
116
|
+
if (!optionIndex.has(token)) {
|
|
117
|
+
const index = props.modelValue.findIndex((value) => selectionKeyToken('SCheckboxGroup', value, 'modelValue') === token)
|
|
118
|
+
throw new Error(`SCheckboxGroup: modelValue[${index}] does not match any option`)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return new Set(modelIndex.keys())
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
function updateOption(value: T, selected: boolean): void {
|
|
125
|
+
if (props.readonly || props.disabled) return
|
|
126
|
+
const targetToken = selectionKeyToken('SCheckboxGroup', value, 'updated option value')
|
|
127
|
+
const nextTokens = new Set(selectedTokens.value)
|
|
128
|
+
if (selected) nextTokens.add(targetToken)
|
|
129
|
+
else nextTokens.delete(targetToken)
|
|
130
|
+
emit('update:modelValue', validatedOptions.value
|
|
131
|
+
.filter((record) => nextTokens.has(record.token))
|
|
132
|
+
.map((record) => record.option.value))
|
|
133
|
+
}
|
|
134
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
@reference "../../../styles/reference.css";
|
|
2
|
+
|
|
3
|
+
.s-choice-cards {
|
|
4
|
+
display: grid;
|
|
5
|
+
gap: 0.75rem;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.s-choice-cards[data-columns='auto'] {
|
|
9
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.s-choice-cards[data-columns='2'] {
|
|
13
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.s-choice-cards[data-columns='3'] {
|
|
17
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.s-choice-card { @apply relative flex min-h-28 min-w-0 flex-col items-start gap-2 overflow-hidden rounded-2xl border border-surface-300 bg-surface-0 p-4 text-left text-surface-800 hover:border-primary-300 hover:bg-primary-50/30 disabled:cursor-not-allowed disabled:opacity-50 dark:border-surface-700 dark:bg-surface-900 dark:text-surface-100; }
|
|
21
|
+
.s-choice-card:focus-visible { outline: var(--s-focus-ring-width) solid var(--s-focus-ring-color); outline-offset: var(--s-focus-ring-offset); }
|
|
22
|
+
.s-choice-card[aria-checked='true'] { @apply border-primary-500 ring-1 ring-primary-500/20; }
|
|
23
|
+
.s-choice-card__kicker { @apply text-[0.7rem] font-semibold uppercase tracking-wide text-surface-500; }
|
|
24
|
+
.s-choice-card__title { @apply text-sm font-semibold leading-5; }
|
|
25
|
+
.s-choice-card__description { @apply text-xs leading-5 text-surface-500; }
|
|
26
|
+
.s-choice-card__meta { @apply mt-auto text-xs leading-4 text-surface-500; }
|
|
27
|
+
.s-choice-card__mark { @apply absolute right-4 top-4 inline-flex h-6 w-6 items-center justify-center rounded-full bg-primary-100 font-bold text-primary-600; }
|
|
28
|
+
.s-choice-cards[data-density='compact'] .s-choice-card { @apply min-h-24 p-3; }
|
|
29
|
+
.s-choice-cards[aria-readonly='true'] .s-choice-card { @apply cursor-default; }
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<SFieldGroup
|
|
3
|
+
v-bind="ownedAttrs.bindings()"
|
|
4
|
+
:id="id"
|
|
5
|
+
:label="label"
|
|
6
|
+
:help-text="helpText"
|
|
7
|
+
:error-message="errorMessage"
|
|
8
|
+
:required="required"
|
|
9
|
+
:invalid="invalid"
|
|
10
|
+
:disabled="disabled"
|
|
11
|
+
:readonly="readonly"
|
|
12
|
+
>
|
|
13
|
+
<template #default="{ describedBy, labelId }">
|
|
14
|
+
<div
|
|
15
|
+
class="s-choice-cards"
|
|
16
|
+
:data-columns="columns"
|
|
17
|
+
:data-density="density"
|
|
18
|
+
:data-selection-indicator="selectionIndicator"
|
|
19
|
+
role="radiogroup"
|
|
20
|
+
:aria-labelledby="labelId"
|
|
21
|
+
:aria-describedby="describedBy"
|
|
22
|
+
:aria-required="required || undefined"
|
|
23
|
+
:aria-invalid="invalid || undefined"
|
|
24
|
+
:aria-disabled="disabled || undefined"
|
|
25
|
+
:aria-readonly="readonly || undefined"
|
|
26
|
+
>
|
|
27
|
+
<button
|
|
28
|
+
v-for="option in options"
|
|
29
|
+
:key="optionToken(option)"
|
|
30
|
+
:ref="(element) => roving.register(option.value, resolveNativeElement(element))"
|
|
31
|
+
class="s-choice-card"
|
|
32
|
+
type="button"
|
|
33
|
+
:disabled="disabled || option.disabled"
|
|
34
|
+
:aria-checked="option.value === modelValue"
|
|
35
|
+
:tabindex="option.value === focusableValue ? 0 : -1"
|
|
36
|
+
role="radio"
|
|
37
|
+
@click="select(option.value)"
|
|
38
|
+
@keydown="roving.handleKeydown($event, option.value)"
|
|
39
|
+
>
|
|
40
|
+
<span v-if="option.kicker" class="s-choice-card__kicker">{{ option.kicker }}</span>
|
|
41
|
+
<span class="s-choice-card__title">{{ option.title }}</span>
|
|
42
|
+
<span v-if="option.description" class="s-choice-card__description">{{ option.description }}</span>
|
|
43
|
+
<span v-if="option.meta" class="s-choice-card__meta">{{ option.meta }}</span>
|
|
44
|
+
<span v-if="selectionIndicator === 'mark' && option.value === modelValue" class="s-choice-card__mark" aria-hidden="true">✓</span>
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
|
48
|
+
<template v-if="$slots.help" #help><slot name="help" /></template>
|
|
49
|
+
<template v-if="$slots.error" #error><slot name="error" /></template>
|
|
50
|
+
</SFieldGroup>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<script lang="ts">
|
|
54
|
+
import type { SelectionKey as PublicSelectionKey } from '../_internal/useSelectionRoving'
|
|
55
|
+
|
|
56
|
+
/** Одна radio-card option. / One radio-card option. */
|
|
57
|
+
export interface ChoiceCardOption<T extends PublicSelectionKey = string> {
|
|
58
|
+
value: T
|
|
59
|
+
title: string
|
|
60
|
+
description?: string
|
|
61
|
+
meta?: string
|
|
62
|
+
kicker?: string
|
|
63
|
+
disabled?: boolean
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Публичный контракт группы карточек выбора. / Public choice-card group contract. */
|
|
67
|
+
export interface Props<T extends PublicSelectionKey = string> {
|
|
68
|
+
modelValue: T | null
|
|
69
|
+
options: readonly ChoiceCardOption<T>[]
|
|
70
|
+
label: string
|
|
71
|
+
id?: string
|
|
72
|
+
helpText?: string
|
|
73
|
+
errorMessage?: string
|
|
74
|
+
required?: boolean
|
|
75
|
+
invalid?: boolean
|
|
76
|
+
disabled?: boolean
|
|
77
|
+
readonly?: boolean
|
|
78
|
+
columns?: 'auto' | '2' | '3'
|
|
79
|
+
density?: 'default' | 'compact'
|
|
80
|
+
selectionIndicator?: 'mark' | 'none'
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<script setup lang="ts" generic="T extends SelectionKey = string">
|
|
85
|
+
import { computed } from 'vue'
|
|
86
|
+
import type { ComponentPublicInstance, VNodeChild } from 'vue'
|
|
87
|
+
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
88
|
+
import { useInteractiveLeafRegistration } from '../../../internal/passiveContentContract'
|
|
89
|
+
import { selectionKeyToken, useSelectionRoving } from '../_internal/useSelectionRoving'
|
|
90
|
+
import type { SelectionKey } from '../_internal/useSelectionRoving'
|
|
91
|
+
import SFieldGroup from './SFieldGroup.vue'
|
|
92
|
+
|
|
93
|
+
defineOptions({ inheritAttrs: false })
|
|
94
|
+
|
|
95
|
+
const props = withDefaults(defineProps<Props<T>>(), {
|
|
96
|
+
id: undefined,
|
|
97
|
+
helpText: undefined,
|
|
98
|
+
errorMessage: undefined,
|
|
99
|
+
required: false,
|
|
100
|
+
invalid: false,
|
|
101
|
+
disabled: false,
|
|
102
|
+
readonly: false,
|
|
103
|
+
columns: 'auto',
|
|
104
|
+
density: 'default',
|
|
105
|
+
selectionIndicator: 'mark',
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const emit = defineEmits<{
|
|
109
|
+
'update:modelValue': [value: T]
|
|
110
|
+
}>()
|
|
111
|
+
defineSlots<{
|
|
112
|
+
/** Дополнительное описание группы. / Supporting group description. */
|
|
113
|
+
help?: () => VNodeChild
|
|
114
|
+
/** Сообщение ошибки при invalid=true. / Error message when invalid=true. */
|
|
115
|
+
error?: () => VNodeChild
|
|
116
|
+
}>()
|
|
117
|
+
const ownedAttrs = useOwnedAttrs({ component: 'SChoiceCards', owner: 'field group' })
|
|
118
|
+
useInteractiveLeafRegistration({ owner: 'SChoiceCards' })
|
|
119
|
+
const select = (value: T): void => {
|
|
120
|
+
if (props.disabled || props.readonly) return
|
|
121
|
+
emit('update:modelValue', value)
|
|
122
|
+
}
|
|
123
|
+
const rovingOptions = computed<readonly ChoiceCardOption<T>[]>(() => (
|
|
124
|
+
props.disabled
|
|
125
|
+
? props.options.map((option) => ({ ...option, disabled: true }))
|
|
126
|
+
: props.options
|
|
127
|
+
))
|
|
128
|
+
const roving = useSelectionRoving({
|
|
129
|
+
options: rovingOptions,
|
|
130
|
+
selectedValue: computed(() => props.modelValue),
|
|
131
|
+
select,
|
|
132
|
+
component: 'SChoiceCards',
|
|
133
|
+
})
|
|
134
|
+
const focusableValue = roving.focusableValue
|
|
135
|
+
const optionToken = (option: ChoiceCardOption<T>): string => selectionKeyToken(
|
|
136
|
+
'SChoiceCards',
|
|
137
|
+
option.value,
|
|
138
|
+
'option.value',
|
|
139
|
+
)
|
|
140
|
+
const resolveNativeElement = (element: Element | ComponentPublicInstance | null): HTMLElement | null => (
|
|
141
|
+
element instanceof HTMLElement ? element : null
|
|
142
|
+
)
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<style lang="postcss" src="./SChoiceCards.css" scoped></style>
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<SSelect
|
|
3
|
+
v-bind="ownedAttrs.bindings()"
|
|
4
|
+
:id="id"
|
|
5
|
+
:model-value="modelValue"
|
|
6
|
+
:options="resolvedOptions"
|
|
7
|
+
selection-mode="single"
|
|
8
|
+
:disabled="disabled"
|
|
9
|
+
:placeholder="computedAriaLabel"
|
|
10
|
+
:title="resolvedTitle"
|
|
11
|
+
:size="size"
|
|
12
|
+
:align="align"
|
|
13
|
+
:trigger-marker="triggerMarker"
|
|
14
|
+
appearance="swatch"
|
|
15
|
+
@update:model-value="updateColor"
|
|
16
|
+
/>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { computed } from 'vue'
|
|
21
|
+
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
22
|
+
import { validateNonEmptyString } from '../../../internal/runtimeContract'
|
|
23
|
+
import SSelect, { type SSelectOption as BaseSelectOption } from './SSelect.vue'
|
|
24
|
+
import type { SMarker, SMarkerTone } from '../_internal/markerContract'
|
|
25
|
+
|
|
26
|
+
interface SColorSelectOptionBase {
|
|
27
|
+
value: string
|
|
28
|
+
label: string
|
|
29
|
+
title?: string
|
|
30
|
+
disabled?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Предопределённый цвет из палитры. / A predefined palette color. */
|
|
34
|
+
export type SColorSelectPresetOption = SColorSelectOptionBase & {
|
|
35
|
+
kind?: 'preset'
|
|
36
|
+
marker: { tone: SMarkerTone }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Явный пользовательский цвет с собственным значением и подписью.
|
|
41
|
+
* An explicit custom color with its own value and label.
|
|
42
|
+
*/
|
|
43
|
+
export type SColorSelectCustomOption = SColorSelectOptionBase & {
|
|
44
|
+
kind: 'custom'
|
|
45
|
+
marker: Extract<SMarker, { tone: 'custom' }>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Закрытый контракт вариантов выбора цвета. / Closed color-selection option contract. */
|
|
49
|
+
export type SColorSelectOption = SColorSelectPresetOption | SColorSelectCustomOption
|
|
50
|
+
defineOptions({ inheritAttrs: false })
|
|
51
|
+
|
|
52
|
+
const props = withDefaults(defineProps<{
|
|
53
|
+
modelValue: string; options: readonly SColorSelectOption[]; id?: string; disabled?: boolean; title?: string;
|
|
54
|
+
ariaLabel?: string; size?: 'sm' | 'md'; align?: 'left' | 'right' | 'center'
|
|
55
|
+
}>(), { id: undefined, disabled: false, title: undefined, ariaLabel: undefined, size: 'md', align: 'left' })
|
|
56
|
+
const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
|
|
57
|
+
const ownedAttrs = useOwnedAttrs({ component: 'SColorSelect', owner: 'combobox trigger' })
|
|
58
|
+
|
|
59
|
+
const allowedOptionKeys = new Set(['kind', 'value', 'label', 'marker', 'title', 'disabled'])
|
|
60
|
+
const resolvedOptions = computed<readonly BaseSelectOption<string>[]>(() => {
|
|
61
|
+
if (!Array.isArray(props.options)) {
|
|
62
|
+
throw new TypeError('SColorSelect: options должен быть массивом. / SColorSelect: options must be an array.')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return props.options.map((option, index) => {
|
|
66
|
+
const candidate: unknown = option
|
|
67
|
+
if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate)) {
|
|
68
|
+
throw new TypeError(
|
|
69
|
+
`SColorSelect: options[${index}] должен быть объектом option. `
|
|
70
|
+
+ `/ SColorSelect: options[${index}] must be an option object`,
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
const unsupportedKey = Object.keys(candidate).find((key) => !allowedOptionKeys.has(key))
|
|
74
|
+
if (unsupportedKey !== undefined) {
|
|
75
|
+
throw new TypeError(
|
|
76
|
+
`SColorSelect: options[${index}].${unsupportedKey} не входит в контракт color option. `
|
|
77
|
+
+ `/ SColorSelect: options[${index}].${unsupportedKey} is not part of the color option contract`,
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
const marker = (candidate as { marker?: SMarker }).marker
|
|
81
|
+
const kind = (candidate as { kind?: unknown }).kind
|
|
82
|
+
if (marker === undefined) {
|
|
83
|
+
throw new TypeError(
|
|
84
|
+
`SColorSelect: options[${index}].marker обязателен. `
|
|
85
|
+
+ `/ SColorSelect: options[${index}].marker is required`,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
if (marker?.tone === 'custom' && kind !== 'custom') {
|
|
89
|
+
throw new TypeError(
|
|
90
|
+
`SColorSelect: options[${index}] с custom marker должен объявлять kind "custom". `
|
|
91
|
+
+ `/ SColorSelect: options[${index}] with a custom marker must declare kind "custom"`,
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
if (kind === 'custom' && marker?.tone !== 'custom') {
|
|
95
|
+
throw new TypeError(
|
|
96
|
+
`SColorSelect: options[${index}] kind "custom" требует custom marker. `
|
|
97
|
+
+ `/ SColorSelect: options[${index}] kind "custom" requires a custom marker`,
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
if (kind !== undefined && kind !== 'preset' && kind !== 'custom') {
|
|
101
|
+
throw new TypeError(
|
|
102
|
+
`SColorSelect: options[${index}].kind должен быть "preset" или "custom". `
|
|
103
|
+
+ `/ SColorSelect: options[${index}].kind must be "preset" or "custom"`,
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const { value, label, title, disabled } = option
|
|
108
|
+
return {
|
|
109
|
+
value,
|
|
110
|
+
label,
|
|
111
|
+
marker,
|
|
112
|
+
...(title === undefined ? {} : { title }),
|
|
113
|
+
...(disabled === undefined ? {} : { disabled }),
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
const selectedOption = computed(() => {
|
|
118
|
+
const modelValue = validateNonEmptyString('SColorSelect', 'modelValue', props.modelValue)
|
|
119
|
+
const option = resolvedOptions.value.find((candidate) => candidate.value === modelValue)
|
|
120
|
+
if (option !== undefined) return option
|
|
121
|
+
throw new Error(
|
|
122
|
+
`SColorSelect: modelValue ${JSON.stringify(modelValue)} отсутствует в options inventory. `
|
|
123
|
+
+ `/ SColorSelect: modelValue ${JSON.stringify(modelValue)} is absent from the options inventory.`,
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
const triggerMarker = computed(() => selectedOption.value.marker)
|
|
127
|
+
const resolvedTitle = computed(() => props.title === undefined
|
|
128
|
+
? undefined
|
|
129
|
+
: validateNonEmptyString('SColorSelect', 'title', props.title))
|
|
130
|
+
const computedAriaLabel = computed(() => {
|
|
131
|
+
if (props.ariaLabel !== undefined) {
|
|
132
|
+
return validateNonEmptyString('SColorSelect', 'ariaLabel', props.ariaLabel)
|
|
133
|
+
}
|
|
134
|
+
if (resolvedTitle.value !== undefined) return resolvedTitle.value
|
|
135
|
+
return validateNonEmptyString('SColorSelect', 'selectedOption.label', selectedOption.value.label)
|
|
136
|
+
})
|
|
137
|
+
const updateColor = (value: string | null | readonly string[]): void => {
|
|
138
|
+
if (typeof value !== 'string') {
|
|
139
|
+
throw new TypeError(
|
|
140
|
+
'SColorSelect: SSelect update должен быть строковым ключом цвета. '
|
|
141
|
+
+ '/ SColorSelect: SSelect update must be a string color key',
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
emit('update:modelValue', value)
|
|
145
|
+
}
|
|
146
|
+
</script>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
@reference "../../../styles/reference.css";
|
|
2
|
+
|
|
3
|
+
.s-combobox-trigger {
|
|
4
|
+
@apply relative inline-flex min-w-0 items-center gap-2 px-3 font-semibold;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.s-combobox-trigger[data-width='full'] { @apply w-full; }
|
|
8
|
+
.s-combobox-trigger[data-width='auto'] { @apply w-auto; }
|
|
9
|
+
.s-combobox-trigger[data-size='xs'] { min-height: var(--s-control-hitbox-xs); @apply px-2 text-xs; }
|
|
10
|
+
.s-combobox-trigger[data-size='sm'] { min-height: var(--s-control-hitbox-sm); @apply px-2.5 text-xs; }
|
|
11
|
+
.s-combobox-trigger[data-size='md'] { min-height: var(--s-control-hitbox-md); @apply text-sm; }
|
|
12
|
+
.s-combobox-trigger[data-appearance='swatch'] { width: var(--s-control-hitbox-md); @apply justify-center rounded-full border border-transparent bg-transparent p-1; box-shadow: none; }
|
|
13
|
+
.s-combobox-trigger[data-appearance='swatch']:disabled { @apply cursor-not-allowed opacity-50; }
|
|
14
|
+
|
|
15
|
+
.s-combobox-trigger__value {
|
|
16
|
+
@apply min-w-0 flex-1 truncate text-left;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.s-combobox-trigger__caret {
|
|
20
|
+
@apply ml-auto inline-flex shrink-0 items-center justify-center text-surface-400;
|
|
21
|
+
width: var(--s-icon-size-sm);
|
|
22
|
+
height: var(--s-icon-size-sm);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
[data-appearance='swatch'] .s-combobox-trigger__caret {
|
|
26
|
+
@apply absolute -bottom-0.5 -right-0.5 rounded-full border border-surface-200 bg-surface-0 text-surface-500;
|
|
27
|
+
@apply dark:border-surface-700 dark:bg-surface-900 dark:text-surface-300;
|
|
28
|
+
width: var(--s-icon-size-xs);
|
|
29
|
+
height: var(--s-icon-size-xs);
|
|
30
|
+
box-shadow: var(--s-elevation-control);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.s-combobox-trigger__caret > svg {
|
|
34
|
+
@apply h-full w-full;
|
|
35
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
v-bind="fieldSurfaceBindings"
|
|
4
|
+
ref="buttonRef"
|
|
5
|
+
type="button"
|
|
6
|
+
:id="id"
|
|
7
|
+
:title="title"
|
|
8
|
+
class="s-combobox-trigger"
|
|
9
|
+
:data-size="size"
|
|
10
|
+
:data-width="width"
|
|
11
|
+
:disabled="disabled"
|
|
12
|
+
role="combobox"
|
|
13
|
+
aria-haspopup="listbox"
|
|
14
|
+
:aria-label="accessibleLabel"
|
|
15
|
+
:aria-expanded="expanded"
|
|
16
|
+
:aria-controls="controls"
|
|
17
|
+
:aria-invalid="invalid || undefined"
|
|
18
|
+
:aria-describedby="describedBy"
|
|
19
|
+
:aria-required="required || undefined"
|
|
20
|
+
:aria-readonly="readonly || undefined"
|
|
21
|
+
:data-testid="testId"
|
|
22
|
+
:data-test="test"
|
|
23
|
+
:data-task-mapping-internal-value="taskMappingInternalValue"
|
|
24
|
+
:data-task-mapping-kind="taskMappingKind"
|
|
25
|
+
:data-appearance="appearance"
|
|
26
|
+
@click="emit('toggle', $event)"
|
|
27
|
+
@keydown.down.prevent="emit('toggle', $event)"
|
|
28
|
+
@keydown.up.prevent="emit('toggle', $event)"
|
|
29
|
+
>
|
|
30
|
+
<SMarker v-if="marker" :marker="marker" :size="appearance === 'swatch' ? 'lg' : 'sm'" />
|
|
31
|
+
<span v-if="appearance === 'field'" class="s-combobox-trigger__value">
|
|
32
|
+
{{ displayValue || placeholder }}
|
|
33
|
+
</span>
|
|
34
|
+
<span class="s-combobox-trigger__caret" aria-hidden="true">
|
|
35
|
+
<ChevronsUpDown v-if="appearance === 'field'" />
|
|
36
|
+
<ChevronDown v-else />
|
|
37
|
+
</span>
|
|
38
|
+
</button>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script setup lang="ts">
|
|
42
|
+
import { computed, ref } from 'vue'
|
|
43
|
+
import { ChevronDown, ChevronsUpDown } from '../../icons/sputnigUiIcons'
|
|
44
|
+
import type { InteractiveElementApi } from '../../../internal/interactiveElement'
|
|
45
|
+
import { useInteractiveLeafRegistration } from '../../../internal/passiveContentContract'
|
|
46
|
+
import { resolveFieldSurfaceBindings } from '../../../internal/fieldSurface'
|
|
47
|
+
import SMarker from './SMarker.vue'
|
|
48
|
+
import type { SMarker as MarkerContract } from '../_internal/markerContract'
|
|
49
|
+
|
|
50
|
+
export interface Props {
|
|
51
|
+
id: string
|
|
52
|
+
accessibleLabel: string
|
|
53
|
+
expanded: boolean
|
|
54
|
+
controls: string
|
|
55
|
+
displayValue?: string
|
|
56
|
+
placeholder?: string
|
|
57
|
+
marker?: MarkerContract
|
|
58
|
+
appearance?: 'field' | 'swatch'
|
|
59
|
+
size?: 'xs' | 'sm' | 'md'
|
|
60
|
+
width?: 'auto' | 'full'
|
|
61
|
+
disabled?: boolean
|
|
62
|
+
invalid?: boolean
|
|
63
|
+
readonly?: boolean
|
|
64
|
+
required?: boolean
|
|
65
|
+
describedBy?: string
|
|
66
|
+
title?: string
|
|
67
|
+
testId?: string | number | boolean
|
|
68
|
+
test?: string | number | boolean
|
|
69
|
+
taskMappingInternalValue?: string | number | boolean
|
|
70
|
+
taskMappingKind?: string | number | boolean
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type SComboboxTriggerApi = InteractiveElementApi<HTMLButtonElement>
|
|
74
|
+
|
|
75
|
+
defineOptions({ inheritAttrs: false })
|
|
76
|
+
|
|
77
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
78
|
+
displayValue: undefined,
|
|
79
|
+
placeholder: 'Выберите значение',
|
|
80
|
+
marker: undefined,
|
|
81
|
+
appearance: 'field',
|
|
82
|
+
size: 'md',
|
|
83
|
+
width: 'full',
|
|
84
|
+
disabled: false,
|
|
85
|
+
invalid: false,
|
|
86
|
+
readonly: false,
|
|
87
|
+
required: false,
|
|
88
|
+
describedBy: undefined,
|
|
89
|
+
title: undefined,
|
|
90
|
+
testId: undefined,
|
|
91
|
+
test: undefined,
|
|
92
|
+
taskMappingInternalValue: undefined,
|
|
93
|
+
taskMappingKind: undefined,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const emit = defineEmits<{
|
|
97
|
+
toggle: [event: MouseEvent | KeyboardEvent]
|
|
98
|
+
}>()
|
|
99
|
+
const buttonRef = ref<HTMLButtonElement | null>(null)
|
|
100
|
+
const fieldSurfaceBindings = computed(() => resolveFieldSurfaceBindings('SComboboxTrigger', {
|
|
101
|
+
kind: 'combobox-trigger',
|
|
102
|
+
appearance: props.appearance === 'field' ? 'field' : 'embedded',
|
|
103
|
+
invalid: props.invalid,
|
|
104
|
+
disabled: props.disabled,
|
|
105
|
+
readonly: props.readonly,
|
|
106
|
+
}))
|
|
107
|
+
|
|
108
|
+
useInteractiveLeafRegistration({ owner: 'SComboboxTrigger' })
|
|
109
|
+
|
|
110
|
+
const getElement = (): HTMLButtonElement => {
|
|
111
|
+
const element = buttonRef.value
|
|
112
|
+
if (!element) throw new Error('SComboboxTrigger: native button element is unavailable')
|
|
113
|
+
return element
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
defineExpose<SComboboxTriggerApi>({
|
|
117
|
+
getElement,
|
|
118
|
+
focus: (options?: FocusOptions) => getElement().focus(options),
|
|
119
|
+
})
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<style lang="postcss" src="./SComboboxTrigger.css" scoped></style>
|