@pgcorp/ui-kit 0.5.0 → 0.7.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 +39 -0
- package/docs/public-api.md +6 -0
- package/package.json +5 -1
- package/src/components/shared/_internal/SHeaderSurface.css +98 -0
- package/src/components/shared/_internal/SHeaderSurface.vue +80 -0
- package/src/components/shared/_internal/SToastItem.css +8 -8
- package/src/components/shared/_internal/SToastItem.vue +8 -1
- package/src/components/shared/_internal/useRadioGroup.ts +106 -0
- package/src/components/shared/containers/SPageHeader.vue +13 -20
- package/src/components/shared/controls/SButton.css +6 -6
- package/src/components/shared/controls/SButton.vue +9 -1
- package/src/components/shared/controls/SCheckbox.vue +14 -43
- package/src/components/shared/controls/SChoiceCards.vue +16 -39
- package/src/components/shared/controls/SField.css +0 -12
- package/src/components/shared/controls/SField.vue +14 -10
- package/src/components/shared/controls/SFieldGroup.css +1 -19
- package/src/components/shared/controls/SFieldGroup.vue +13 -10
- package/src/components/shared/controls/SFieldLabel.css +0 -1
- package/src/components/shared/controls/SFieldLabel.vue +2 -1
- package/src/components/shared/controls/SInteractiveSurface.css +3 -3
- package/src/components/shared/controls/SInteractiveSurface.vue +9 -2
- package/src/components/shared/controls/SMarker.css +16 -16
- package/src/components/shared/controls/SMarker.vue +27 -1
- package/src/components/shared/controls/SSegmentedControl.vue +15 -43
- package/src/components/shared/controls/SSwitch.vue +14 -32
- package/src/components/shared/data-display/SActionListItem.css +2 -2
- package/src/components/shared/data-display/SActionListItem.vue +8 -1
- package/src/components/shared/data-display/SBadge.vue +41 -18
- package/src/components/shared/data-display/SChip.vue +22 -10
- package/src/components/shared/data-display/SMessage.css +8 -8
- package/src/components/shared/data-display/SMessage.vue +8 -1
- package/src/components/shared/data-display/SProgressBar.css +12 -12
- package/src/components/shared/data-display/SProgressBar.vue +12 -6
- package/src/components/shared/data-display/SProgressIndicator.css +5 -5
- package/src/components/shared/data-display/SProgressIndicator.vue +9 -1
- package/src/components/shared/data-display/SSectionHeader.vue +17 -21
- package/src/components/shared/data-display/SStatus.css +10 -10
- package/src/components/shared/data-display/SStatus.vue +9 -1
- package/src/components/shared/data-display/STable.vue +27 -4
- package/src/components/shared/data-display/STooltip.css +4 -4
- package/src/components/shared/data-display/STooltip.vue +159 -29
- package/src/components/shared/data-display/STooltipTarget.css +26 -0
- package/src/components/shared/data-display/STooltipTarget.vue +94 -0
- package/src/components/shared/database/SDataGrid.vue +13 -0
- package/src/components/shared/navigation/STabs.vue +11 -3
- package/src/internal/fieldPresentation.ts +28 -0
- package/src/internal/ownedAttrs.ts +3 -0
- package/src/internal/pillSurface.ts +93 -0
- package/src/internal/semanticTone.ts +23 -0
- package/src/internal/useBinaryInput.ts +76 -0
- package/src/styles/style.css +114 -0
- package/src/styles/tokens.css +31 -0
- package/src/components/shared/containers/SPageHeader.css +0 -82
- package/src/components/shared/data-display/SBadge.css +0 -73
- package/src/components/shared/data-display/SChip.css +0 -59
- package/src/components/shared/data-display/SSectionHeader.css +0 -84
|
@@ -43,11 +43,10 @@
|
|
|
43
43
|
</template>
|
|
44
44
|
|
|
45
45
|
<script setup lang="ts">
|
|
46
|
-
import { computed, onMounted, ref, useId, watch } from 'vue'
|
|
47
46
|
import { Check, Minus } from '../../icons/sputnigUiIcons'
|
|
48
47
|
import { mergeIdReferences, useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
49
48
|
import { useInteractiveLeafRegistration } from '../../../internal/passiveContentContract'
|
|
50
|
-
import {
|
|
49
|
+
import { useBinaryInput } from '../../../internal/useBinaryInput'
|
|
51
50
|
import SField from './SField.vue'
|
|
52
51
|
|
|
53
52
|
defineOptions({ inheritAttrs: false })
|
|
@@ -83,48 +82,20 @@ const emit = defineEmits<{
|
|
|
83
82
|
}>()
|
|
84
83
|
const ownedAttrs = useOwnedAttrs({ component: 'SCheckbox', owner: 'native checkbox input' })
|
|
85
84
|
useInteractiveLeafRegistration({ owner: 'SCheckbox' })
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
return props.modelValue
|
|
85
|
+
const {
|
|
86
|
+
checkedValue,
|
|
87
|
+
inputEl,
|
|
88
|
+
inputId,
|
|
89
|
+
onChange,
|
|
90
|
+
onClick,
|
|
91
|
+
} = useBinaryInput({
|
|
92
|
+
owner: 'SCheckbox',
|
|
93
|
+
inputName: 'checkbox',
|
|
94
|
+
props,
|
|
95
|
+
indeterminate: () => props.indeterminate,
|
|
96
|
+
emitUpdate: (value) => emit('update:modelValue', value),
|
|
97
|
+
emitClick: (event) => emit('click', event),
|
|
101
98
|
})
|
|
102
|
-
|
|
103
|
-
function applyIndeterminate(): void {
|
|
104
|
-
if (inputEl.value) {
|
|
105
|
-
inputEl.value.indeterminate = props.indeterminate && !checkedValue.value
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
onMounted(applyIndeterminate)
|
|
110
|
-
watch([() => props.indeterminate, checkedValue], applyIndeterminate)
|
|
111
|
-
|
|
112
|
-
function onChange(event: Event): void {
|
|
113
|
-
if (props.readonly) return
|
|
114
|
-
const target = event.currentTarget
|
|
115
|
-
if (!(target instanceof HTMLInputElement)) {
|
|
116
|
-
throw new TypeError('SCheckbox: change event owner must be the native checkbox input')
|
|
117
|
-
}
|
|
118
|
-
emit('update:modelValue', target.checked)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function onClick(event: MouseEvent): void {
|
|
122
|
-
if (props.readonly) {
|
|
123
|
-
event.preventDefault()
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
emit('click', event)
|
|
127
|
-
}
|
|
128
99
|
</script>
|
|
129
100
|
|
|
130
101
|
<style lang="postcss" src="./SCheckbox.css" scoped></style>
|
|
@@ -16,26 +16,19 @@
|
|
|
16
16
|
:data-columns="columns"
|
|
17
17
|
:data-density="density"
|
|
18
18
|
:data-selection-indicator="selectionIndicator"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
:aria-readonly="readonly || undefined"
|
|
19
|
+
v-bind="radioGroup.groupBindings({
|
|
20
|
+
labelledBy: labelId,
|
|
21
|
+
describedBy,
|
|
22
|
+
required,
|
|
23
|
+
invalid,
|
|
24
|
+
})"
|
|
26
25
|
>
|
|
27
26
|
<button
|
|
28
27
|
v-for="option in options"
|
|
29
|
-
:key="optionToken(option)"
|
|
30
|
-
:ref="
|
|
28
|
+
:key="radioGroup.optionToken(option.value)"
|
|
29
|
+
:ref="radioGroup.optionRef(option.value)"
|
|
31
30
|
class="s-choice-card"
|
|
32
|
-
|
|
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)"
|
|
31
|
+
v-bind="radioGroup.optionBindings(option)"
|
|
39
32
|
>
|
|
40
33
|
<span v-if="option.kicker" class="s-choice-card__kicker">{{ option.kicker }}</span>
|
|
41
34
|
<span class="s-choice-card__title">{{ option.title }}</span>
|
|
@@ -83,10 +76,10 @@ export interface Props<T extends PublicSelectionKey = string> {
|
|
|
83
76
|
|
|
84
77
|
<script setup lang="ts" generic="T extends SelectionKey = string">
|
|
85
78
|
import { computed } from 'vue'
|
|
86
|
-
import type {
|
|
79
|
+
import type { VNodeChild } from 'vue'
|
|
87
80
|
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
88
81
|
import { useInteractiveLeafRegistration } from '../../../internal/passiveContentContract'
|
|
89
|
-
import {
|
|
82
|
+
import { useRadioGroup } from '../_internal/useRadioGroup'
|
|
90
83
|
import type { SelectionKey } from '../_internal/useSelectionRoving'
|
|
91
84
|
import SFieldGroup from './SFieldGroup.vue'
|
|
92
85
|
|
|
@@ -116,30 +109,14 @@ defineSlots<{
|
|
|
116
109
|
}>()
|
|
117
110
|
const ownedAttrs = useOwnedAttrs({ component: 'SChoiceCards', owner: 'field group' })
|
|
118
111
|
useInteractiveLeafRegistration({ owner: 'SChoiceCards' })
|
|
119
|
-
const
|
|
120
|
-
|
|
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,
|
|
112
|
+
const radioGroup = useRadioGroup({
|
|
113
|
+
options: computed(() => props.options),
|
|
130
114
|
selectedValue: computed(() => props.modelValue),
|
|
131
|
-
|
|
115
|
+
disabled: computed(() => props.disabled),
|
|
116
|
+
readonly: computed(() => props.readonly),
|
|
117
|
+
update: (value) => emit('update:modelValue', value),
|
|
132
118
|
component: 'SChoiceCards',
|
|
133
119
|
})
|
|
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
120
|
</script>
|
|
144
121
|
|
|
145
122
|
<style lang="postcss" src="./SChoiceCards.css" scoped></style>
|
|
@@ -53,24 +53,12 @@
|
|
|
53
53
|
@apply flex items-center;
|
|
54
54
|
}
|
|
55
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
56
|
.s-field__help {
|
|
67
57
|
grid-area: help;
|
|
68
|
-
@apply text-surface-500 dark:text-surface-400;
|
|
69
58
|
}
|
|
70
59
|
|
|
71
60
|
.s-field__error {
|
|
72
61
|
grid-area: error;
|
|
73
|
-
@apply text-danger-600 dark:text-danger-400;
|
|
74
62
|
}
|
|
75
63
|
|
|
76
64
|
.s-field[data-disabled='true'] .s-field__label,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
>
|
|
11
11
|
<label v-if="hasLabel" :id="labelId" class="s-field__label" :for="controlId">
|
|
12
12
|
<slot name="label">{{ label }}</slot>
|
|
13
|
-
<span v-if="required" class="s-field__required" aria-hidden="true">*</span>
|
|
13
|
+
<span v-if="required" :class="['s-field__required', FIELD_REQUIRED_CLASS]" aria-hidden="true">*</span>
|
|
14
14
|
</label>
|
|
15
15
|
|
|
16
16
|
<div class="s-field__control">
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
/>
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
|
-
<p v-if="helpText || $slots.help" :id="helpId" class="s-field__help">
|
|
28
|
+
<p v-if="helpText || $slots.help" :id="helpId" :class="['s-field__help', FIELD_HELP_CLASS]">
|
|
29
29
|
<slot name="help">{{ helpText }}</slot>
|
|
30
30
|
</p>
|
|
31
|
-
<p v-if="isInvalid && (errorMessage || $slots.error)" :id="errorId" class="s-field__error">
|
|
31
|
+
<p v-if="isInvalid && (errorMessage || $slots.error)" :id="errorId" :class="['s-field__error', FIELD_ERROR_CLASS]">
|
|
32
32
|
<slot name="error">{{ errorMessage }}</slot>
|
|
33
33
|
</p>
|
|
34
34
|
</div>
|
|
@@ -36,6 +36,12 @@
|
|
|
36
36
|
|
|
37
37
|
<script setup lang="ts">
|
|
38
38
|
import { computed, useSlots } from 'vue'
|
|
39
|
+
import {
|
|
40
|
+
FIELD_ERROR_CLASS,
|
|
41
|
+
FIELD_HELP_CLASS,
|
|
42
|
+
FIELD_REQUIRED_CLASS,
|
|
43
|
+
useFieldSupportingText,
|
|
44
|
+
} from '../../../internal/fieldPresentation'
|
|
39
45
|
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
40
46
|
|
|
41
47
|
defineOptions({ inheritAttrs: false })
|
|
@@ -76,13 +82,11 @@ const ownedAttrs = useOwnedAttrs({ component: 'SField', owner: 'field root' })
|
|
|
76
82
|
const isInvalid = computed(() => props.invalid)
|
|
77
83
|
const hasLabel = computed(() => Boolean(props.label || slots.label))
|
|
78
84
|
const labelId = computed(() => `${props.controlId}-label`)
|
|
79
|
-
const
|
|
80
|
-
const errorId =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (isInvalid.value && (props.errorMessage || slots.error)) ids.push(errorId.value)
|
|
85
|
-
return ids.length > 0 ? ids.join(' ') : undefined
|
|
85
|
+
const fieldId = computed(() => props.controlId)
|
|
86
|
+
const { describedBy, errorId, helpId } = useFieldSupportingText({
|
|
87
|
+
baseId: fieldId,
|
|
88
|
+
hasHelp: () => Boolean(props.helpText || slots.help),
|
|
89
|
+
hasError: () => Boolean(isInvalid.value && (props.errorMessage || slots.error)),
|
|
86
90
|
})
|
|
87
91
|
</script>
|
|
88
92
|
|
|
@@ -8,10 +8,6 @@
|
|
|
8
8
|
@apply mb-2 min-w-0 text-sm font-medium text-surface-700 dark:text-surface-300;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
.s-field-group__required {
|
|
12
|
-
@apply ml-0.5 text-danger-600 dark:text-danger-400;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
11
|
.s-field-group__controls {
|
|
16
12
|
@apply grid min-w-0 gap-2;
|
|
17
13
|
}
|
|
@@ -32,23 +28,9 @@
|
|
|
32
28
|
@apply grid-cols-4;
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
.s-field-group__help,
|
|
36
|
-
.s-field-group__error {
|
|
37
|
-
@apply mt-1 min-w-0 whitespace-normal text-xs;
|
|
38
|
-
overflow-wrap: anywhere;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.s-field-group__help {
|
|
42
|
-
@apply text-surface-500 dark:text-surface-400;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.s-field-group__error {
|
|
46
|
-
@apply text-danger-600 dark:text-danger-400;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
31
|
.s-field-group[data-disabled='true'] .s-field-group__legend,
|
|
50
32
|
.s-field-group[data-readonly='true'] .s-field-group__legend {
|
|
51
|
-
|
|
33
|
+
color: var(--s-field-disabled-foreground);
|
|
52
34
|
}
|
|
53
35
|
|
|
54
36
|
@media (max-width: 40rem) {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
>
|
|
15
15
|
<legend :id="labelId" class="s-field-group__legend">
|
|
16
16
|
<slot name="label">{{ validatedLabel }}</slot>
|
|
17
|
-
<span v-if="required" class="s-field-group__required" aria-hidden="true">*</span>
|
|
17
|
+
<span v-if="required" :class="['s-field-group__required', FIELD_REQUIRED_CLASS]" aria-hidden="true">*</span>
|
|
18
18
|
</legend>
|
|
19
19
|
|
|
20
20
|
<div class="s-field-group__controls">
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
/>
|
|
30
30
|
</div>
|
|
31
31
|
|
|
32
|
-
<p v-if="helpText || $slots.help" :id="helpId" class="s-field-group__help">
|
|
32
|
+
<p v-if="helpText || $slots.help" :id="helpId" :class="['s-field-group__help', FIELD_HELP_CLASS]">
|
|
33
33
|
<slot name="help">{{ helpText }}</slot>
|
|
34
34
|
</p>
|
|
35
|
-
<p v-if="invalid && (errorMessage || $slots.error)" :id="errorId" class="s-field-group__error">
|
|
35
|
+
<p v-if="invalid && (errorMessage || $slots.error)" :id="errorId" :class="['s-field-group__error', FIELD_ERROR_CLASS]">
|
|
36
36
|
<slot name="error">{{ errorMessage }}</slot>
|
|
37
37
|
</p>
|
|
38
38
|
</fieldset>
|
|
@@ -40,6 +40,12 @@
|
|
|
40
40
|
|
|
41
41
|
<script setup lang="ts">
|
|
42
42
|
import { computed, useId, useSlots } from 'vue'
|
|
43
|
+
import {
|
|
44
|
+
FIELD_ERROR_CLASS,
|
|
45
|
+
FIELD_HELP_CLASS,
|
|
46
|
+
FIELD_REQUIRED_CLASS,
|
|
47
|
+
useFieldSupportingText,
|
|
48
|
+
} from '../../../internal/fieldPresentation'
|
|
43
49
|
import { mergeIdReferences, useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
44
50
|
import {
|
|
45
51
|
validateBoundedInteger,
|
|
@@ -92,13 +98,10 @@ const groupId = computed(() => validateDomId(
|
|
|
92
98
|
))
|
|
93
99
|
const labelId = computed(() => `${groupId.value}-legend`)
|
|
94
100
|
const validatedLabel = computed(() => validateNonEmptyString('SFieldGroup', 'label', props.label))
|
|
95
|
-
const helpId =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (props.helpText || slots.help) ids.push(helpId.value)
|
|
100
|
-
if (props.invalid && (props.errorMessage || slots.error)) ids.push(errorId.value)
|
|
101
|
-
return ids.length > 0 ? ids.join(' ') : undefined
|
|
101
|
+
const { describedBy, errorId, helpId } = useFieldSupportingText({
|
|
102
|
+
baseId: groupId,
|
|
103
|
+
hasHelp: () => Boolean(props.helpText || slots.help),
|
|
104
|
+
hasError: () => Boolean(props.invalid && (props.errorMessage || slots.error)),
|
|
102
105
|
})
|
|
103
106
|
const validatedLayout = computed<SFieldGroupLayout>(() => validateExactString(
|
|
104
107
|
'SFieldGroup',
|
|
@@ -9,4 +9,3 @@
|
|
|
9
9
|
.s-field-label[data-size='md'] { @apply text-sm; }
|
|
10
10
|
.s-field-label[data-align='end'] { @apply text-right; }
|
|
11
11
|
.s-field-label[data-disabled='true'] { @apply text-surface-400 dark:text-surface-500; }
|
|
12
|
-
.s-field-label__required { @apply ml-0.5 text-danger-600 dark:text-danger-400; }
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
:data-disabled="disabled || undefined"
|
|
9
9
|
>
|
|
10
10
|
<slot />
|
|
11
|
-
<span v-if="required" class="s-field-label__required" aria-hidden="true">*</span>
|
|
11
|
+
<span v-if="required" :class="['s-field-label__required', FIELD_REQUIRED_CLASS]" aria-hidden="true">*</span>
|
|
12
12
|
</label>
|
|
13
13
|
</template>
|
|
14
14
|
|
|
15
15
|
<script setup lang="ts">
|
|
16
16
|
import type { VNodeChild } from 'vue'
|
|
17
|
+
import { FIELD_REQUIRED_CLASS } from '../../../internal/fieldPresentation'
|
|
17
18
|
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
18
19
|
|
|
19
20
|
defineOptions({ inheritAttrs: false })
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
.s-interactive-surface[data-radius='md'] { @apply rounded-md; }
|
|
52
52
|
.s-interactive-surface[data-radius='lg'] { @apply rounded-lg; }
|
|
53
53
|
.s-interactive-surface[data-radius='xl'] { @apply rounded-xl; }
|
|
54
|
-
.s-interactive-surface[data-tone='primary'] { @apply border-primary-200 bg-primary-500/5 dark:border-primary-500/30 dark:bg-primary-500/10; }
|
|
55
|
-
.s-interactive-surface[data-tone='success'] { @apply border-success-200 bg-success-50 dark:border-success-800 dark:bg-success-950; }
|
|
56
|
-
.s-interactive-surface[data-tone='danger'] { @apply border-danger-200 bg-danger-50 dark:border-danger-800 dark:bg-danger-950; }
|
|
54
|
+
.s-interactive-surface[data-s-tone='primary'] { @apply border-primary-200 bg-primary-500/5 dark:border-primary-500/30 dark:bg-primary-500/10; }
|
|
55
|
+
.s-interactive-surface[data-s-tone='success'] { @apply border-success-200 bg-success-50 dark:border-success-800 dark:bg-success-950; }
|
|
56
|
+
.s-interactive-surface[data-s-tone='danger'] { @apply border-danger-200 bg-danger-50 dark:border-danger-800 dark:bg-danger-950; }
|
|
57
57
|
.s-interactive-surface[data-selected='true'] { @apply border-primary-400 bg-primary-50 dark:border-primary-600 dark:bg-primary-950; }
|
|
58
58
|
.s-interactive-surface[data-pressed='true'] { @apply border-primary-500 bg-primary-100 dark:border-primary-500 dark:bg-primary-950/70; }
|
|
59
59
|
.s-interactive-surface[data-disabled='true'] { @apply cursor-not-allowed opacity-60; }
|
|
@@ -5,6 +5,7 @@ import type { STooltipTriggerBinding } from '../data-display/STooltip.vue'
|
|
|
5
5
|
import { hasOwn } from '../../../internal/es2020'
|
|
6
6
|
import { resolveLinkTarget } from '../../../internal/linkTarget'
|
|
7
7
|
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
8
|
+
import { resolveSemanticToneBinding } from '../../../internal/semanticTone'
|
|
8
9
|
import {
|
|
9
10
|
PassiveContentBoundary,
|
|
10
11
|
useInteractiveLeafRegistration,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
defineOptions({ inheritAttrs: false })
|
|
15
16
|
|
|
16
17
|
export type InteractiveSurfaceTone = 'default' | 'primary' | 'success' | 'danger'
|
|
18
|
+
const INTERACTIVE_SURFACE_TONES = ['default', 'primary', 'success', 'danger'] as const satisfies readonly InteractiveSurfaceTone[]
|
|
17
19
|
export type InteractiveSurfaceDensity = 'compact' | 'comfortable'
|
|
18
20
|
export type InteractiveSurfacePadding = 'none' | 'sm' | 'md'
|
|
19
21
|
export type InteractiveSurfaceRadius = 'md' | 'lg' | 'xl'
|
|
@@ -84,6 +86,12 @@ defineSlots<{
|
|
|
84
86
|
}>()
|
|
85
87
|
|
|
86
88
|
const ownedAttrs = useOwnedAttrs({ component: 'SInteractiveSurface', owner: 'interactive surface owner' })
|
|
89
|
+
const toneBinding = computed(() => resolveSemanticToneBinding(
|
|
90
|
+
'SInteractiveSurface',
|
|
91
|
+
'tone',
|
|
92
|
+
props.tone,
|
|
93
|
+
INTERACTIVE_SURFACE_TONES,
|
|
94
|
+
))
|
|
87
95
|
|
|
88
96
|
const assertOptionalBoolean = (surface: Record<string, unknown>, field: 'disabled' | 'pressed'): void => {
|
|
89
97
|
if (surface[field] !== undefined && typeof surface[field] !== 'boolean') {
|
|
@@ -213,7 +221,6 @@ const validatedTooltipTrigger = computed<STooltipTriggerBinding | undefined>(()
|
|
|
213
221
|
}
|
|
214
222
|
return candidate as STooltipTriggerBinding
|
|
215
223
|
})
|
|
216
|
-
|
|
217
224
|
const activationBindings = (): Readonly<Record<string, unknown>> => ({
|
|
218
225
|
...ownedAttrs.bindings(),
|
|
219
226
|
...(validatedTooltipTrigger.value ?? {}),
|
|
@@ -282,9 +289,9 @@ function activateAction(event: MouseEvent): void {
|
|
|
282
289
|
<template>
|
|
283
290
|
<div
|
|
284
291
|
v-bind="surfaceMode === 'static' ? ownedAttrs.bindings() : undefined"
|
|
292
|
+
:data-s-tone="toneBinding['data-s-tone']"
|
|
285
293
|
class="s-interactive-surface"
|
|
286
294
|
:data-mode="surfaceMode"
|
|
287
|
-
:data-tone="tone"
|
|
288
295
|
:data-density="density"
|
|
289
296
|
:data-padding="padding"
|
|
290
297
|
:data-radius="radius"
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
@reference "../../../styles/reference.css";
|
|
2
2
|
.s-marker { @apply inline-block h-2 w-2 shrink-0 rounded-full bg-surface-400 ring-1 ring-inset ring-surface-500/50; }
|
|
3
3
|
.s-marker[data-size='lg'] { @apply h-7 w-7 ring-2 ring-white/80 shadow-sm dark:ring-surface-950/80; }
|
|
4
|
-
.s-marker[data-tone='primary'] { @apply bg-primary-500 ring-primary-600/50; }
|
|
5
|
-
.s-marker[data-tone='success'] { @apply bg-success-500 ring-success-600/50; }
|
|
6
|
-
.s-marker[data-tone='blue'] { @apply bg-blue-400 ring-blue-500/50; }
|
|
7
|
-
.s-marker[data-tone='sky'] { @apply bg-sky-400 ring-sky-500/50; }
|
|
8
|
-
.s-marker[data-tone='indigo'] { @apply bg-indigo-400 ring-indigo-500/50; }
|
|
9
|
-
.s-marker[data-tone='emerald'] { @apply bg-emerald-400 ring-emerald-500/50; }
|
|
10
|
-
.s-marker[data-tone='green'] { @apply bg-green-400 ring-green-500/50; }
|
|
11
|
-
.s-marker[data-tone='teal'] { @apply bg-teal-400 ring-teal-500/50; }
|
|
12
|
-
.s-marker[data-tone='amber'] { @apply bg-amber-400 ring-amber-500/50; }
|
|
13
|
-
.s-marker[data-tone='orange'] { @apply bg-orange-400 ring-orange-500/50; }
|
|
14
|
-
.s-marker[data-tone='rose'] { @apply bg-rose-400 ring-rose-500/50; }
|
|
15
|
-
.s-marker[data-tone='pink'] { @apply bg-pink-400 ring-pink-500/50; }
|
|
16
|
-
.s-marker[data-tone='fuchsia'] { @apply bg-fuchsia-400 ring-fuchsia-500/50; }
|
|
17
|
-
.s-marker[data-tone='cyan'] { @apply bg-cyan-400 ring-cyan-500/50; }
|
|
18
|
-
.s-marker[data-tone='lime'] { @apply bg-lime-400 ring-lime-500/50; }
|
|
19
|
-
.s-marker[data-tone='custom'] { background-color: var(--s-marker-custom-color); --tw-ring-color: var(--s-marker-custom-color); }
|
|
4
|
+
.s-marker[data-s-tone='primary'] { @apply bg-primary-500 ring-primary-600/50; }
|
|
5
|
+
.s-marker[data-s-tone='success'] { @apply bg-success-500 ring-success-600/50; }
|
|
6
|
+
.s-marker[data-s-tone='blue'] { @apply bg-blue-400 ring-blue-500/50; }
|
|
7
|
+
.s-marker[data-s-tone='sky'] { @apply bg-sky-400 ring-sky-500/50; }
|
|
8
|
+
.s-marker[data-s-tone='indigo'] { @apply bg-indigo-400 ring-indigo-500/50; }
|
|
9
|
+
.s-marker[data-s-tone='emerald'] { @apply bg-emerald-400 ring-emerald-500/50; }
|
|
10
|
+
.s-marker[data-s-tone='green'] { @apply bg-green-400 ring-green-500/50; }
|
|
11
|
+
.s-marker[data-s-tone='teal'] { @apply bg-teal-400 ring-teal-500/50; }
|
|
12
|
+
.s-marker[data-s-tone='amber'] { @apply bg-amber-400 ring-amber-500/50; }
|
|
13
|
+
.s-marker[data-s-tone='orange'] { @apply bg-orange-400 ring-orange-500/50; }
|
|
14
|
+
.s-marker[data-s-tone='rose'] { @apply bg-rose-400 ring-rose-500/50; }
|
|
15
|
+
.s-marker[data-s-tone='pink'] { @apply bg-pink-400 ring-pink-500/50; }
|
|
16
|
+
.s-marker[data-s-tone='fuchsia'] { @apply bg-fuchsia-400 ring-fuchsia-500/50; }
|
|
17
|
+
.s-marker[data-s-tone='cyan'] { @apply bg-cyan-400 ring-cyan-500/50; }
|
|
18
|
+
.s-marker[data-s-tone='lime'] { @apply bg-lime-400 ring-lime-500/50; }
|
|
19
|
+
.s-marker[data-s-tone='custom'] { background-color: var(--s-marker-custom-color); --tw-ring-color: var(--s-marker-custom-color); }
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<span
|
|
3
3
|
v-bind="ownedAttrs.bindings()"
|
|
4
|
+
:data-s-tone="toneBinding['data-s-tone']"
|
|
4
5
|
class="s-marker"
|
|
5
|
-
:data-tone="marker.tone"
|
|
6
6
|
:data-size="size"
|
|
7
7
|
:style="customProperties"
|
|
8
8
|
aria-hidden="true"
|
|
@@ -17,12 +17,38 @@ export type { SCustomMarker, SMarker, SMarkerTone } from '../_internal/markerCon
|
|
|
17
17
|
<script setup lang="ts">
|
|
18
18
|
import { computed } from 'vue'
|
|
19
19
|
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
20
|
+
import { resolveSemanticToneBinding } from '../../../internal/semanticTone'
|
|
20
21
|
import { createCustomMarker, type SMarker } from '../_internal/markerContract'
|
|
21
22
|
|
|
22
23
|
defineOptions({ inheritAttrs: false })
|
|
23
24
|
|
|
24
25
|
const props = withDefaults(defineProps<{ marker: SMarker; size?: 'sm' | 'lg' }>(), { size: 'sm' })
|
|
25
26
|
const ownedAttrs = useOwnedAttrs({ component: 'SMarker', owner: 'marker root' })
|
|
27
|
+
const MARKER_TONES = [
|
|
28
|
+
'surface',
|
|
29
|
+
'primary',
|
|
30
|
+
'success',
|
|
31
|
+
'blue',
|
|
32
|
+
'sky',
|
|
33
|
+
'indigo',
|
|
34
|
+
'emerald',
|
|
35
|
+
'green',
|
|
36
|
+
'teal',
|
|
37
|
+
'amber',
|
|
38
|
+
'orange',
|
|
39
|
+
'rose',
|
|
40
|
+
'pink',
|
|
41
|
+
'fuchsia',
|
|
42
|
+
'cyan',
|
|
43
|
+
'lime',
|
|
44
|
+
'custom',
|
|
45
|
+
] as const
|
|
46
|
+
const toneBinding = computed(() => resolveSemanticToneBinding(
|
|
47
|
+
'SMarker',
|
|
48
|
+
'marker.tone',
|
|
49
|
+
props.marker.tone,
|
|
50
|
+
MARKER_TONES,
|
|
51
|
+
))
|
|
26
52
|
const customProperties = computed(() => {
|
|
27
53
|
if (props.marker.tone !== 'custom') return undefined
|
|
28
54
|
createCustomMarker(props.marker.color)
|
|
@@ -17,27 +17,21 @@
|
|
|
17
17
|
:data-option-wrap="optionWrap"
|
|
18
18
|
:data-invalid="group.invalid || undefined"
|
|
19
19
|
:data-readonly="group.readonly || undefined"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
v-bind="radioGroup.groupBindings({
|
|
21
|
+
labelledBy: group.labelId,
|
|
22
|
+
describedBy: group.describedBy,
|
|
23
|
+
required: group.required,
|
|
24
|
+
invalid: group.invalid,
|
|
25
|
+
})"
|
|
26
26
|
>
|
|
27
27
|
<button
|
|
28
28
|
v-for="option in options"
|
|
29
|
-
:key="optionToken(option)"
|
|
30
|
-
:ref="
|
|
29
|
+
:key="radioGroup.optionToken(option.value)"
|
|
30
|
+
:ref="radioGroup.optionRef(option.value)"
|
|
31
31
|
class="s-segmented-control__item"
|
|
32
32
|
:data-size="size"
|
|
33
|
-
type="button"
|
|
34
|
-
:aria-checked="option.value === modelValue"
|
|
35
|
-
:disabled="group.disabled || option.disabled"
|
|
36
|
-
role="radio"
|
|
37
|
-
:tabindex="option.value === focusableValue ? 0 : -1"
|
|
38
33
|
:title="option.title"
|
|
39
|
-
|
|
40
|
-
@keydown="roving.handleKeydown($event, option.value)"
|
|
34
|
+
v-bind="radioGroup.optionBindings(option)"
|
|
41
35
|
>{{ option.label }}</button>
|
|
42
36
|
</div>
|
|
43
37
|
</template>
|
|
@@ -74,10 +68,9 @@ export interface Props<T extends PublicSelectionKey = string> {
|
|
|
74
68
|
|
|
75
69
|
<script setup lang="ts" generic="T extends SelectionKey = string">
|
|
76
70
|
import { computed } from 'vue'
|
|
77
|
-
import type { ComponentPublicInstance } from 'vue'
|
|
78
71
|
import { useOwnedAttrs } from '../../../internal/ownedAttrs'
|
|
79
72
|
import { useInteractiveLeafRegistration } from '../../../internal/passiveContentContract'
|
|
80
|
-
import {
|
|
73
|
+
import { useRadioGroup } from '../_internal/useRadioGroup'
|
|
81
74
|
import type { SelectionKey } from '../_internal/useSelectionRoving'
|
|
82
75
|
import SFieldGroup from './SFieldGroup.vue'
|
|
83
76
|
|
|
@@ -100,35 +93,14 @@ const emit = defineEmits<{
|
|
|
100
93
|
}>()
|
|
101
94
|
const ownedAttrs = useOwnedAttrs({ component: 'SSegmentedControl', owner: 'field group root' })
|
|
102
95
|
useInteractiveLeafRegistration({ owner: 'SSegmentedControl' })
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (props.disabled || props.readonly || option.disabled) {
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
emit('update:modelValue', option.value)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const rovingOptions = computed(() => props.options.map((option) => ({
|
|
112
|
-
value: option.value,
|
|
113
|
-
disabled: props.disabled || option.disabled,
|
|
114
|
-
})))
|
|
115
|
-
const roving = useSelectionRoving({
|
|
116
|
-
options: rovingOptions,
|
|
96
|
+
const radioGroup = useRadioGroup({
|
|
97
|
+
options: computed(() => props.options),
|
|
117
98
|
selectedValue: computed(() => props.modelValue),
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
99
|
+
disabled: computed(() => props.disabled),
|
|
100
|
+
readonly: computed(() => props.readonly),
|
|
101
|
+
update: (value) => emit('update:modelValue', value),
|
|
121
102
|
component: 'SSegmentedControl',
|
|
122
103
|
})
|
|
123
|
-
const focusableValue = roving.focusableValue
|
|
124
|
-
const optionToken = (option: SegmentedControlOption<T>): string => selectionKeyToken(
|
|
125
|
-
'SSegmentedControl',
|
|
126
|
-
option.value,
|
|
127
|
-
'option.value',
|
|
128
|
-
)
|
|
129
|
-
const resolveNativeElement = (element: Element | ComponentPublicInstance | null): HTMLElement | null => (
|
|
130
|
-
element instanceof HTMLElement ? element : null
|
|
131
|
-
)
|
|
132
104
|
</script>
|
|
133
105
|
|
|
134
106
|
<style lang="postcss" src="./SSegmentedControl.css" scoped></style>
|