@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.
Files changed (56) hide show
  1. package/README.md +39 -0
  2. package/docs/public-api.md +6 -0
  3. package/package.json +5 -1
  4. package/src/components/shared/_internal/SHeaderSurface.css +98 -0
  5. package/src/components/shared/_internal/SHeaderSurface.vue +80 -0
  6. package/src/components/shared/_internal/SToastItem.css +8 -8
  7. package/src/components/shared/_internal/SToastItem.vue +8 -1
  8. package/src/components/shared/_internal/useRadioGroup.ts +106 -0
  9. package/src/components/shared/containers/SPageHeader.vue +13 -20
  10. package/src/components/shared/controls/SButton.css +6 -6
  11. package/src/components/shared/controls/SButton.vue +9 -1
  12. package/src/components/shared/controls/SCheckbox.vue +14 -43
  13. package/src/components/shared/controls/SChoiceCards.vue +16 -39
  14. package/src/components/shared/controls/SField.css +0 -12
  15. package/src/components/shared/controls/SField.vue +14 -10
  16. package/src/components/shared/controls/SFieldGroup.css +1 -19
  17. package/src/components/shared/controls/SFieldGroup.vue +13 -10
  18. package/src/components/shared/controls/SFieldLabel.css +0 -1
  19. package/src/components/shared/controls/SFieldLabel.vue +2 -1
  20. package/src/components/shared/controls/SInteractiveSurface.css +3 -3
  21. package/src/components/shared/controls/SInteractiveSurface.vue +9 -2
  22. package/src/components/shared/controls/SMarker.css +16 -16
  23. package/src/components/shared/controls/SMarker.vue +27 -1
  24. package/src/components/shared/controls/SSegmentedControl.vue +15 -43
  25. package/src/components/shared/controls/SSwitch.vue +14 -32
  26. package/src/components/shared/data-display/SActionListItem.css +2 -2
  27. package/src/components/shared/data-display/SActionListItem.vue +8 -1
  28. package/src/components/shared/data-display/SBadge.vue +41 -18
  29. package/src/components/shared/data-display/SChip.vue +22 -10
  30. package/src/components/shared/data-display/SMessage.css +8 -8
  31. package/src/components/shared/data-display/SMessage.vue +8 -1
  32. package/src/components/shared/data-display/SProgressBar.css +12 -12
  33. package/src/components/shared/data-display/SProgressBar.vue +12 -6
  34. package/src/components/shared/data-display/SProgressIndicator.css +5 -5
  35. package/src/components/shared/data-display/SProgressIndicator.vue +9 -1
  36. package/src/components/shared/data-display/SSectionHeader.vue +17 -21
  37. package/src/components/shared/data-display/SStatus.css +10 -10
  38. package/src/components/shared/data-display/SStatus.vue +9 -1
  39. package/src/components/shared/data-display/STable.vue +27 -4
  40. package/src/components/shared/data-display/STooltip.css +4 -4
  41. package/src/components/shared/data-display/STooltip.vue +159 -29
  42. package/src/components/shared/data-display/STooltipTarget.css +26 -0
  43. package/src/components/shared/data-display/STooltipTarget.vue +94 -0
  44. package/src/components/shared/database/SDataGrid.vue +13 -0
  45. package/src/components/shared/navigation/STabs.vue +11 -3
  46. package/src/internal/fieldPresentation.ts +28 -0
  47. package/src/internal/ownedAttrs.ts +3 -0
  48. package/src/internal/pillSurface.ts +93 -0
  49. package/src/internal/semanticTone.ts +23 -0
  50. package/src/internal/useBinaryInput.ts +76 -0
  51. package/src/styles/style.css +114 -0
  52. package/src/styles/tokens.css +31 -0
  53. package/src/components/shared/containers/SPageHeader.css +0 -82
  54. package/src/components/shared/data-display/SBadge.css +0 -73
  55. package/src/components/shared/data-display/SChip.css +0 -59
  56. package/src/components/shared/data-display/SSectionHeader.css +0 -84
@@ -13,6 +13,7 @@
13
13
  <template #default="field">
14
14
  <span class="s-switch-control">
15
15
  <input
16
+ ref="inputEl"
16
17
  v-bind="ownedAttrs.bindings()"
17
18
  :id="field.controlId"
18
19
  type="checkbox"
@@ -37,10 +38,9 @@
37
38
  </template>
38
39
 
39
40
  <script setup lang="ts">
40
- import { computed, useId } from 'vue'
41
41
  import { mergeIdReferences, useOwnedAttrs } from '../../../internal/ownedAttrs'
42
42
  import { useInteractiveLeafRegistration } from '../../../internal/passiveContentContract'
43
- import { resolveOptionalDomId } from '../../../internal/runtimeContract'
43
+ import { useBinaryInput } from '../../../internal/useBinaryInput'
44
44
  import SField from './SField.vue'
45
45
 
46
46
  defineOptions({ inheritAttrs: false })
@@ -73,37 +73,19 @@ const emit = defineEmits<{
73
73
  }>()
74
74
  const ownedAttrs = useOwnedAttrs({ component: 'SSwitch', owner: 'native switch input' })
75
75
  useInteractiveLeafRegistration({ owner: 'SSwitch' })
76
-
77
- const generatedInputId = useId()
78
- const inputId = computed(() => resolveOptionalDomId(
79
- 'SSwitch',
80
- 'id',
81
- props.id,
82
- `s-switch-${generatedInputId}`,
83
- ))
84
- const checkedValue = computed(() => {
85
- if (typeof props.modelValue !== 'boolean') {
86
- throw new TypeError('SSwitch: modelValue must be boolean')
87
- }
88
- return props.modelValue
76
+ const {
77
+ checkedValue,
78
+ inputEl,
79
+ inputId,
80
+ onChange,
81
+ onClick,
82
+ } = useBinaryInput({
83
+ owner: 'SSwitch',
84
+ inputName: 'switch',
85
+ props,
86
+ emitUpdate: (value) => emit('update:modelValue', value),
87
+ emitClick: (event) => emit('click', event),
89
88
  })
90
-
91
- function onChange(event: Event): void {
92
- if (props.readonly) return
93
- const target = event.currentTarget
94
- if (!(target instanceof HTMLInputElement)) {
95
- throw new TypeError('SSwitch: change event owner must be the native switch input')
96
- }
97
- emit('update:modelValue', target.checked)
98
- }
99
-
100
- function onClick(event: MouseEvent): void {
101
- if (props.readonly) {
102
- event.preventDefault()
103
- return
104
- }
105
- emit('click', event)
106
- }
107
89
  </script>
108
90
 
109
91
  <style lang="postcss" src="./SSwitch.css" scoped></style>
@@ -28,11 +28,11 @@
28
28
  @apply dark:bg-primary-900 dark:text-primary-200;
29
29
  }
30
30
 
31
- .s-action-list-item[data-tone='success'] .s-action-list-item__icon {
31
+ .s-action-list-item[data-s-tone='success'] .s-action-list-item__icon {
32
32
  @apply bg-success-100 text-success-700 dark:bg-success-900 dark:text-success-200;
33
33
  }
34
34
 
35
- .s-action-list-item[data-tone='danger'] .s-action-list-item__icon {
35
+ .s-action-list-item[data-s-tone='danger'] .s-action-list-item__icon {
36
36
  @apply bg-danger-100 text-danger-700 dark:bg-danger-900 dark:text-danger-200;
37
37
  }
38
38
 
@@ -2,6 +2,7 @@
2
2
  import { computed } from 'vue'
3
3
  import type { Component, VNodeChild } from 'vue'
4
4
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
5
+ import { resolveSemanticToneBinding } from '../../../internal/semanticTone'
5
6
  import { validateBoundedInteger, validateNonEmptyString } from '../../../internal/runtimeContract'
6
7
 
7
8
  import SButton from '../controls/SButton.vue'
@@ -102,6 +103,12 @@ const itemStyle = computed(() => ({
102
103
  '--s-action-list-item-indent': `${validatedIndentLevel.value * 0.875}rem`,
103
104
  }))
104
105
  const ownedAttrs = useOwnedAttrs({ component: 'SActionListItem', owner: 'action list item root' })
106
+ const toneBinding = computed(() => resolveSemanticToneBinding(
107
+ 'SActionListItem',
108
+ 'tone',
109
+ props.tone,
110
+ ['default', 'primary', 'success', 'danger'] as const,
111
+ ))
105
112
  const rootBindings = computed(() => ownedAttrs.bindings())
106
113
  const semanticRole = computed(() => {
107
114
  const declaredRole = rootBindings.value.role
@@ -163,9 +170,9 @@ function emitAction(event: MouseEvent): void {
163
170
  <template>
164
171
  <div
165
172
  v-bind="rootBindings"
173
+ :data-s-tone="toneBinding['data-s-tone']"
166
174
  class="s-action-list-item"
167
175
  :data-selected="selected"
168
- :data-tone="tone"
169
176
  :data-density="density"
170
177
  :data-interactive="resolvedMode !== 'static'"
171
178
  :data-mode="resolvedMode"
@@ -1,42 +1,56 @@
1
1
  <template>
2
2
  <span
3
- v-bind="ownedAttrs.bindings()"
3
+ v-bind="{ ...ownedAttrs.bindings(), ...surfaceBindings }"
4
4
  class="s-badge"
5
- :class="{ 's-badge--dot': dot }"
6
- :data-severity="severity"
7
- :data-size="size"
8
- :data-max-width="maxWidth"
9
- :data-truncate="truncate || undefined"
10
- :data-icon-motion="iconMotion"
11
5
  >
12
6
  <template v-if="!dot">
13
- <span v-if="leadingIcon" class="s-badge__icon" aria-hidden="true">
14
- <component :is="leadingIcon" class="s-badge__icon-graphic" />
7
+ <span v-if="leadingIcon" :class="PILL_ICON_CLASS" aria-hidden="true">
8
+ <component
9
+ :is="leadingIcon"
10
+ :class="[
11
+ PILL_ICON_GRAPHIC_CLASS,
12
+ { [PILL_ICON_SPIN_CLASS]: iconMotion === 'spin' },
13
+ ]"
14
+ />
15
+ </span>
16
+ <span :class="[PILL_LABEL_CLASS, { [PILL_LABEL_TRUNCATE_CLASS]: truncate }]">
17
+ <slot />
15
18
  </span>
16
- <span class="s-badge__label"><slot /></span>
17
19
  </template>
18
20
  </span>
19
21
  </template>
20
22
 
21
23
  <script setup lang="ts">
22
- import type { Component } from 'vue';
24
+ import { computed, type Component } from 'vue';
23
25
  import { useOwnedAttrs } from '../../../internal/ownedAttrs';
26
+ import {
27
+ PILL_ICON_CLASS,
28
+ PILL_ICON_GRAPHIC_CLASS,
29
+ PILL_ICON_SPIN_CLASS,
30
+ PILL_LABEL_CLASS,
31
+ PILL_LABEL_TRUNCATE_CLASS,
32
+ resolvePillSurfaceBindings,
33
+ type PillSurfaceIconMotion,
34
+ type PillSurfaceMaxWidth,
35
+ type PillSurfaceSeverity,
36
+ type PillSurfaceSize,
37
+ } from '../../../internal/pillSurface';
24
38
 
25
39
  defineOptions({ inheritAttrs: false });
26
40
 
27
- export type BadgeSeverity = 'primary' | 'secondary' | 'success' | 'info' | 'warn' | 'danger' | 'contrast';
41
+ export type BadgeSeverity = PillSurfaceSeverity;
28
42
 
29
43
  export interface Props {
30
44
  severity?: BadgeSeverity;
31
45
  dot?: boolean;
32
- size?: 'xs' | 'sm' | 'md';
46
+ size?: PillSurfaceSize;
33
47
  leadingIcon?: Component;
34
- iconMotion?: 'static' | 'spin';
48
+ iconMotion?: PillSurfaceIconMotion;
35
49
  truncate?: boolean;
36
- maxWidth?: 'none' | 'sm' | 'md';
50
+ maxWidth?: PillSurfaceMaxWidth;
37
51
  }
38
52
 
39
- withDefaults(defineProps<Props>(), {
53
+ const props = withDefaults(defineProps<Props>(), {
40
54
  severity: 'secondary',
41
55
  dot: false,
42
56
  size: 'md',
@@ -47,6 +61,15 @@ withDefaults(defineProps<Props>(), {
47
61
  });
48
62
 
49
63
  const ownedAttrs = useOwnedAttrs({ component: 'SBadge', owner: 'badge root' });
64
+ const surfaceBindings = computed(() => resolvePillSurfaceBindings('SBadge', {
65
+ kind: 'badge',
66
+ severity: props.severity,
67
+ size: props.size,
68
+ dot: props.dot,
69
+ closable: false,
70
+ disabled: false,
71
+ truncate: props.truncate,
72
+ maxWidth: props.maxWidth,
73
+ iconMotion: props.iconMotion,
74
+ }));
50
75
  </script>
51
-
52
- <style lang="postcss" src="./SBadge.css" scoped></style>
@@ -1,16 +1,12 @@
1
1
  <template>
2
2
  <span
3
- v-bind="ownedAttrs.bindings()"
3
+ v-bind="{ ...ownedAttrs.bindings(), ...surfaceBindings }"
4
4
  class="s-chip"
5
- :data-severity="validatedSeverity"
6
- :data-size="validatedSize"
7
- :data-closable="closable || undefined"
8
- :data-disabled="disabled || undefined"
9
5
  >
10
- <span v-if="leadingIcon" class="s-chip__icon" aria-hidden="true">
11
- <component :is="leadingIcon" class="s-chip__icon-graphic" />
6
+ <span v-if="leadingIcon" :class="PILL_ICON_CLASS" aria-hidden="true">
7
+ <component :is="leadingIcon" :class="PILL_ICON_GRAPHIC_CLASS" />
12
8
  </span>
13
- <span class="s-chip__label">{{ validatedLabel }}</span>
9
+ <span :class="[PILL_LABEL_CLASS, PILL_LABEL_TRUNCATE_CLASS]">{{ validatedLabel }}</span>
14
10
  <SButton
15
11
  v-if="closable"
16
12
  label=""
@@ -34,6 +30,13 @@
34
30
  import { computed, type Component } from 'vue'
35
31
  import { X } from '../../icons/sputnigUiIcons'
36
32
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
33
+ import {
34
+ PILL_ICON_CLASS,
35
+ PILL_ICON_GRAPHIC_CLASS,
36
+ PILL_LABEL_CLASS,
37
+ PILL_LABEL_TRUNCATE_CLASS,
38
+ resolvePillSurfaceBindings,
39
+ } from '../../../internal/pillSurface'
37
40
  import {
38
41
  validateBoolean,
39
42
  validateExactString,
@@ -80,6 +83,17 @@ const validatedSeverity = computed(() => validateExactString(
80
83
  const validatedSize = computed(() => validateExactString(
81
84
  'SChip', 'size', props.size, ['sm', 'md'] as const,
82
85
  ))
86
+ const surfaceBindings = computed(() => resolvePillSurfaceBindings('SChip', {
87
+ kind: 'chip',
88
+ severity: validatedSeverity.value,
89
+ size: validatedSize.value,
90
+ dot: false,
91
+ closable: props.closable,
92
+ disabled: props.disabled,
93
+ truncate: true,
94
+ maxWidth: 'none',
95
+ iconMotion: 'static',
96
+ }))
83
97
  const resolvedCloseLabel = computed(() => {
84
98
  validateBoolean('SChip', 'closable', props.closable)
85
99
  validateBoolean('SChip', 'disabled', props.disabled)
@@ -88,5 +102,3 @@ const resolvedCloseLabel = computed(() => {
88
102
  : validateNonEmptyString('SChip', 'closeLabel', props.closeLabel)
89
103
  })
90
104
  </script>
91
-
92
- <style lang="postcss" src="./SChip.css" scoped></style>
@@ -52,37 +52,37 @@
52
52
  }
53
53
 
54
54
  /* Severity: info */
55
- .s-message[data-severity='info'] {
55
+ .s-message[data-s-tone='info'] {
56
56
  @apply bg-info-50 border-info-200 text-info-800;
57
57
  @apply dark:bg-info-950 dark:border-info-800 dark:text-info-200;
58
58
  }
59
- .s-message[data-severity='info'] .s-message-icon {
59
+ .s-message[data-s-tone='info'] .s-message-icon {
60
60
  @apply text-info-500 dark:text-info-400;
61
61
  }
62
62
 
63
63
  /* Severity: success */
64
- .s-message[data-severity='success'] {
64
+ .s-message[data-s-tone='success'] {
65
65
  @apply bg-success-50 border-success-200 text-success-800;
66
66
  @apply dark:bg-success-950 dark:border-success-800 dark:text-success-200;
67
67
  }
68
- .s-message[data-severity='success'] .s-message-icon {
68
+ .s-message[data-s-tone='success'] .s-message-icon {
69
69
  @apply text-success-500 dark:text-success-400;
70
70
  }
71
71
 
72
72
  /* Severity: warn */
73
- .s-message[data-severity='warn'] {
73
+ .s-message[data-s-tone='warn'] {
74
74
  @apply bg-warn-50 border-warn-200 text-warn-800;
75
75
  @apply dark:bg-warn-950 dark:border-warn-800 dark:text-warn-200;
76
76
  }
77
- .s-message[data-severity='warn'] .s-message-icon {
77
+ .s-message[data-s-tone='warn'] .s-message-icon {
78
78
  @apply text-warn-500 dark:text-warn-400;
79
79
  }
80
80
 
81
81
  /* Severity: danger */
82
- .s-message[data-severity='danger'] {
82
+ .s-message[data-s-tone='danger'] {
83
83
  @apply bg-danger-50 border-danger-200 text-danger-800;
84
84
  @apply dark:bg-danger-950 dark:border-danger-800 dark:text-danger-200;
85
85
  }
86
- .s-message[data-severity='danger'] .s-message-icon {
86
+ .s-message[data-s-tone='danger'] .s-message-icon {
87
87
  @apply text-danger-500 dark:text-danger-400;
88
88
  }
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <div
3
3
  v-bind="ownedAttrs.bindings()"
4
+ :data-s-tone="toneBinding['data-s-tone']"
4
5
  class="s-message"
5
- :data-severity="validatedSeverity"
6
6
  :data-size="validatedSize"
7
7
  :role="liveAttributes.role"
8
8
  :aria-live="liveAttributes.ariaLive"
@@ -43,6 +43,7 @@
43
43
  import { computed } from 'vue'
44
44
  import { X } from '../../icons/sputnigUiIcons'
45
45
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
46
+ import { resolveSemanticToneBinding } from '../../../internal/semanticTone'
46
47
  import {
47
48
  resolveFeedbackLive,
48
49
  resolveFeedbackPresentation,
@@ -112,6 +113,12 @@ const validatedSeverity = computed<Severity>(() => validateExactString(
112
113
  props.severity,
113
114
  MESSAGE_SEVERITIES,
114
115
  ))
116
+ const toneBinding = computed(() => resolveSemanticToneBinding(
117
+ 'SMessage',
118
+ 'severity',
119
+ validatedSeverity.value,
120
+ MESSAGE_SEVERITIES,
121
+ ))
115
122
  const validatedLive = computed<FeedbackLiveMode>(() => validateExactString(
116
123
  'SMessage',
117
124
  'live',
@@ -22,22 +22,22 @@
22
22
  }
23
23
 
24
24
  /* Severity Colors */
25
- .s-progress-bar-value[data-severity='primary'] {
25
+ .s-progress-bar-value[data-s-tone='primary'] {
26
26
  background-color: var(--s-progress-bar-fill-primary);
27
27
  }
28
- .s-progress-bar-value[data-severity='success'] {
28
+ .s-progress-bar-value[data-s-tone='success'] {
29
29
  background-color: var(--s-progress-bar-fill-success);
30
30
  }
31
- .s-progress-bar-value[data-severity='info'] {
31
+ .s-progress-bar-value[data-s-tone='info'] {
32
32
  background-color: var(--s-progress-bar-fill-info);
33
33
  }
34
- .s-progress-bar-value[data-severity='warn'] {
34
+ .s-progress-bar-value[data-s-tone='warn'] {
35
35
  background-color: var(--s-progress-bar-fill-warn);
36
36
  }
37
- .s-progress-bar-value[data-severity='danger'] {
37
+ .s-progress-bar-value[data-s-tone='danger'] {
38
38
  background-color: var(--s-progress-bar-fill-danger);
39
39
  }
40
- .s-progress-bar-value[data-severity='neutral'] {
40
+ .s-progress-bar-value[data-s-tone='neutral'] {
41
41
  background-color: var(--s-progress-bar-fill-neutral);
42
42
  }
43
43
 
@@ -64,21 +64,21 @@
64
64
  }
65
65
 
66
66
  /* Indeterminate Severity Colors */
67
- .s-progress-bar-value--indeterminate[data-severity='primary']::before {
67
+ .s-progress-bar-value--indeterminate[data-s-tone='primary']::before {
68
68
  background-color: var(--s-progress-bar-fill-primary);
69
69
  }
70
- .s-progress-bar-value--indeterminate[data-severity='success']::before {
70
+ .s-progress-bar-value--indeterminate[data-s-tone='success']::before {
71
71
  background-color: var(--s-progress-bar-fill-success);
72
72
  }
73
- .s-progress-bar-value--indeterminate[data-severity='info']::before {
73
+ .s-progress-bar-value--indeterminate[data-s-tone='info']::before {
74
74
  background-color: var(--s-progress-bar-fill-info);
75
75
  }
76
- .s-progress-bar-value--indeterminate[data-severity='warn']::before {
76
+ .s-progress-bar-value--indeterminate[data-s-tone='warn']::before {
77
77
  background-color: var(--s-progress-bar-fill-warn);
78
78
  }
79
- .s-progress-bar-value--indeterminate[data-severity='danger']::before {
79
+ .s-progress-bar-value--indeterminate[data-s-tone='danger']::before {
80
80
  background-color: var(--s-progress-bar-fill-danger);
81
81
  }
82
- .s-progress-bar-value--indeterminate[data-severity='neutral']::before {
82
+ .s-progress-bar-value--indeterminate[data-s-tone='neutral']::before {
83
83
  background-color: var(--s-progress-bar-indeterminate-neutral-fill);
84
84
  }
@@ -14,8 +14,8 @@
14
14
  >
15
15
  <div
16
16
  v-if="mode === 'determinate'"
17
+ :data-s-tone="toneBinding['data-s-tone']"
17
18
  class="s-progress-bar-value"
18
- :data-severity="severity"
19
19
  :style="{ width: `${value}%` }"
20
20
  >
21
21
  <span v-if="showValue" class="s-progress-bar-label">
@@ -24,19 +24,21 @@
24
24
  </div>
25
25
  <div
26
26
  v-else
27
+ :data-s-tone="toneBinding['data-s-tone']"
27
28
  class="s-progress-bar-value--indeterminate"
28
- :data-severity="severity"
29
29
  ></div>
30
30
  </div>
31
31
  </template>
32
32
 
33
33
  <script setup lang="ts">
34
- import { watchEffect } from 'vue';
34
+ import { computed, watchEffect } from 'vue';
35
35
  import { useOwnedAttrs } from '../../../internal/ownedAttrs';
36
+ import { resolveSemanticToneBinding } from '../../../internal/semanticTone';
36
37
 
37
38
  defineOptions({ inheritAttrs: false });
38
39
 
39
40
  export type Severity = 'primary' | 'success' | 'info' | 'warn' | 'danger' | 'neutral';
41
+ const PROGRESS_SEVERITIES = ['primary', 'success', 'info', 'warn', 'danger', 'neutral'] as const satisfies readonly Severity[];
40
42
  export type ProgressBarMode = 'determinate' | 'indeterminate';
41
43
  export type SProgressBarAccessibility =
42
44
  | { readonly mode: 'decorative' }
@@ -62,6 +64,12 @@ const props = withDefaults(defineProps<Props>(), {
62
64
  });
63
65
 
64
66
  const ownedAttrs = useOwnedAttrs({ component: 'SProgressBar', owner: 'progressbar root' });
67
+ const toneBinding = computed(() => resolveSemanticToneBinding(
68
+ 'SProgressBar',
69
+ 'severity',
70
+ props.severity,
71
+ PROGRESS_SEVERITIES,
72
+ ));
65
73
 
66
74
  watchEffect(() => {
67
75
  if (!Number.isFinite(props.value) || props.value < 0 || props.value > 100) {
@@ -70,9 +78,7 @@ watchEffect(() => {
70
78
  if (!['determinate', 'indeterminate'].includes(props.mode)) {
71
79
  throw new TypeError(`SProgressBar: неизвестный mode "${String(props.mode)}"`);
72
80
  }
73
- if (!['primary', 'success', 'info', 'warn', 'danger', 'neutral'].includes(props.severity)) {
74
- throw new TypeError(`SProgressBar: неизвестный severity "${String(props.severity)}"`);
75
- }
81
+ void toneBinding.value;
76
82
  if (!['xs', 'sm', 'md'].includes(props.size)) {
77
83
  throw new TypeError(`SProgressBar: неизвестный size "${String(props.size)}"`);
78
84
  }
@@ -23,11 +23,11 @@
23
23
  .s-progress-indicator[data-size='md'] { --s-progress-indicator-size: var(--s-icon-size-md); }
24
24
  .s-progress-indicator[data-size='lg'] { --s-progress-indicator-size: var(--s-icon-size-lg); }
25
25
 
26
- .s-progress-indicator[data-tone='neutral'] { color: var(--s-progress-indicator-foreground-neutral); }
27
- .s-progress-indicator[data-tone='info'] { color: var(--s-progress-indicator-foreground-info); }
28
- .s-progress-indicator[data-tone='success'] { color: var(--s-progress-indicator-foreground-success); }
29
- .s-progress-indicator[data-tone='warn'] { color: var(--s-progress-indicator-foreground-warn); }
30
- .s-progress-indicator[data-tone='danger'] { color: var(--s-progress-indicator-foreground-danger); }
26
+ .s-progress-indicator[data-s-tone='neutral'] { color: var(--s-progress-indicator-foreground-neutral); }
27
+ .s-progress-indicator[data-s-tone='info'] { color: var(--s-progress-indicator-foreground-info); }
28
+ .s-progress-indicator[data-s-tone='success'] { color: var(--s-progress-indicator-foreground-success); }
29
+ .s-progress-indicator[data-s-tone='warn'] { color: var(--s-progress-indicator-foreground-warn); }
30
+ .s-progress-indicator[data-s-tone='danger'] { color: var(--s-progress-indicator-foreground-danger); }
31
31
 
32
32
  .s-progress-indicator__graphic {
33
33
  width: 100%;
@@ -1,9 +1,9 @@
1
1
  <template>
2
2
  <span
3
3
  v-bind="ownedAttrs.bindings()"
4
+ :data-s-tone="toneBinding['data-s-tone']"
4
5
  class="s-progress-indicator"
5
6
  :data-size="size"
6
- :data-tone="tone"
7
7
  :data-motion="presentation.motion"
8
8
  :data-show-label="validatedShowLabel || undefined"
9
9
  :role="liveAttributes.role"
@@ -30,12 +30,14 @@ import { computed, watchEffect } from 'vue'
30
30
 
31
31
  import { resolveFeedbackIndicator, resolveFeedbackLive, type FeedbackLiveMode } from '../../../internal/feedbackPresentation'
32
32
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
33
+ import { resolveSemanticToneBinding } from '../../../internal/semanticTone'
33
34
  import { validateBoolean } from '../../../internal/runtimeContract'
34
35
 
35
36
  defineOptions({ inheritAttrs: false })
36
37
 
37
38
  export type SProgressIndicatorSize = 'xs' | 'sm' | 'md' | 'lg'
38
39
  export type SProgressIndicatorTone = 'current' | 'neutral' | 'info' | 'success' | 'warn' | 'danger'
40
+ const PROGRESS_INDICATOR_TONES = ['current', 'neutral', 'info', 'success', 'warn', 'danger'] as const satisfies readonly SProgressIndicatorTone[]
39
41
 
40
42
  /**
41
43
  * Декоративный progress принадлежит labelled parent; standalone status требует собственного имени.
@@ -64,6 +66,12 @@ const props = withDefaults(defineProps<Props>(), {
64
66
  })
65
67
 
66
68
  const ownedAttrs = useOwnedAttrs({ component: 'SProgressIndicator', owner: 'progress indicator root' })
69
+ const toneBinding = computed(() => resolveSemanticToneBinding(
70
+ 'SProgressIndicator',
71
+ 'tone',
72
+ props.tone,
73
+ PROGRESS_INDICATOR_TONES,
74
+ ))
67
75
  const presentation = resolveFeedbackIndicator('progress')
68
76
  const validatedShowLabel = computed(() => validateBoolean('SProgressIndicator', 'showLabel', props.showLabel))
69
77
  const feedbackTone = computed(() => props.tone === 'current' ? 'info' : props.tone)
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
3
+ import SHeaderSurface from '../_internal/SHeaderSurface.vue'
3
4
 
4
5
  defineOptions({ inheritAttrs: false })
5
6
 
@@ -32,27 +33,22 @@ const ownedAttrs = useOwnedAttrs({ component: 'SSectionHeader', owner: 'section
32
33
  </script>
33
34
 
34
35
  <template>
35
- <header
36
+ <SHeaderSurface
36
37
  v-bind="ownedAttrs.bindings()"
37
- class="s-section-header"
38
- :data-size="size"
39
- :data-variant="variant"
40
- :data-divider="divider || undefined"
41
- :data-padding="padding"
42
- :data-spacing-after="spacingAfter"
43
- :data-trailing-layout="trailingLayout"
38
+ kind="section"
39
+ :title="title"
40
+ :eyebrow="eyebrow"
41
+ :description="description"
42
+ :meta="meta"
43
+ :heading-level="headingLevel"
44
+ :size="size"
45
+ :variant="variant"
46
+ :divider="divider"
47
+ :padding="padding"
48
+ :spacing-after="spacingAfter"
49
+ :trailing-layout="trailingLayout"
44
50
  >
45
- <div class="s-section-header__copy">
46
- <div v-if="eyebrow" class="s-section-header__eyebrow">{{ eyebrow }}</div>
47
- <component :is="`h${headingLevel}`" class="s-section-header__title">{{ title }}</component>
48
- <p v-if="description" class="s-section-header__description">{{ description }}</p>
49
- <p v-if="meta" class="s-section-header__meta">{{ meta }}</p>
50
- </div>
51
- <div v-if="$slots.badges || $slots.actions" class="s-section-header__trailing">
52
- <div v-if="$slots.badges" class="s-section-header__badges"><slot name="badges" /></div>
53
- <div v-if="$slots.actions" class="s-section-header__actions"><slot name="actions" /></div>
54
- </div>
55
- </header>
51
+ <template v-if="$slots.badges" #badges><slot name="badges" /></template>
52
+ <template v-if="$slots.actions" #actions><slot name="actions" /></template>
53
+ </SHeaderSurface>
56
54
  </template>
57
-
58
- <style lang="postcss" src="./SSectionHeader.css" scoped></style>
@@ -49,42 +49,42 @@
49
49
  @apply sr-only;
50
50
  }
51
51
 
52
- .s-status[data-tone='neutral'] {
52
+ .s-status[data-s-tone='neutral'] {
53
53
  @apply text-surface-500 dark:text-surface-400;
54
54
  }
55
55
 
56
- .s-status[data-tone='info'] {
56
+ .s-status[data-s-tone='info'] {
57
57
  @apply text-info-700 dark:text-info-300;
58
58
  }
59
59
 
60
- .s-status[data-tone='success'] {
60
+ .s-status[data-s-tone='success'] {
61
61
  @apply text-success-700 dark:text-success-300;
62
62
  }
63
63
 
64
- .s-status[data-tone='warn'] {
64
+ .s-status[data-s-tone='warn'] {
65
65
  @apply text-warn-700 dark:text-warn-300;
66
66
  }
67
67
 
68
- .s-status[data-tone='danger'] {
68
+ .s-status[data-s-tone='danger'] {
69
69
  @apply text-danger-700 dark:text-danger-300;
70
70
  }
71
71
 
72
- .s-status[data-appearance='soft'][data-tone='neutral'] {
72
+ .s-status[data-appearance='soft'][data-s-tone='neutral'] {
73
73
  @apply border-surface-200 bg-surface-100 dark:border-surface-700 dark:bg-surface-800;
74
74
  }
75
75
 
76
- .s-status[data-appearance='soft'][data-tone='info'] {
76
+ .s-status[data-appearance='soft'][data-s-tone='info'] {
77
77
  @apply border-info-200 bg-info-50 dark:border-info-800 dark:bg-info-950;
78
78
  }
79
79
 
80
- .s-status[data-appearance='soft'][data-tone='success'] {
80
+ .s-status[data-appearance='soft'][data-s-tone='success'] {
81
81
  @apply border-success-200 bg-success-50 dark:border-success-800 dark:bg-success-950;
82
82
  }
83
83
 
84
- .s-status[data-appearance='soft'][data-tone='warn'] {
84
+ .s-status[data-appearance='soft'][data-s-tone='warn'] {
85
85
  @apply border-warn-200 bg-warn-50 dark:border-warn-800 dark:bg-warn-950;
86
86
  }
87
87
 
88
- .s-status[data-appearance='soft'][data-tone='danger'] {
88
+ .s-status[data-appearance='soft'][data-s-tone='danger'] {
89
89
  @apply border-danger-200 bg-danger-50 dark:border-danger-800 dark:bg-danger-950;
90
90
  }
@@ -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-status"
5
- :data-tone="tone"
6
6
  :data-appearance="appearance"
7
7
  :data-size="size"
8
8
  :data-label-visibility="labelVisibility"
@@ -22,6 +22,7 @@
22
22
  <script setup lang="ts">
23
23
  import { computed, markRaw, toRaw, type Component } from 'vue'
24
24
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
25
+ import { resolveSemanticToneBinding } from '../../../internal/semanticTone'
25
26
 
26
27
  import {
27
28
  resolveFeedbackLive,
@@ -33,6 +34,7 @@ import {
33
34
  defineOptions({ inheritAttrs: false })
34
35
 
35
36
  export type StatusTone = FeedbackTone
37
+ const STATUS_TONES = ['neutral', 'info', 'success', 'warn', 'danger'] as const satisfies readonly StatusTone[]
36
38
  export type StatusAppearance = 'text' | 'soft'
37
39
  export type StatusSize = 'xs' | 'sm' | 'md'
38
40
  export type StatusLiveMode = Exclude<FeedbackLiveMode, 'auto'>
@@ -66,6 +68,12 @@ const props = withDefaults(defineProps<{
66
68
 
67
69
  const defaultIcon = computed<Component>(() => resolveFeedbackPresentation(props.tone).icon)
68
70
  const ownedAttrs = useOwnedAttrs({ component: 'SStatus', owner: 'status root' })
71
+ const toneBinding = computed(() => resolveSemanticToneBinding(
72
+ 'SStatus',
73
+ 'tone',
74
+ props.tone,
75
+ STATUS_TONES,
76
+ ))
69
77
  const liveAttributes = computed(() => resolveFeedbackLive(props.tone, props.live))
70
78
 
71
79
  const resolvedIcon = computed<Component | null>(() => {