@pgcorp/ui-kit 0.6.0 → 0.7.1

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 (32) hide show
  1. package/README.md +57 -0
  2. package/docs/public-api.md +6 -0
  3. package/docs/theming.md +58 -0
  4. package/package.json +5 -1
  5. package/src/components/shared/_internal/SToastItem.css +8 -8
  6. package/src/components/shared/_internal/SToastItem.vue +8 -1
  7. package/src/components/shared/controls/SButton.css +6 -6
  8. package/src/components/shared/controls/SButton.vue +9 -1
  9. package/src/components/shared/controls/SInteractiveSurface.css +3 -3
  10. package/src/components/shared/controls/SInteractiveSurface.vue +9 -2
  11. package/src/components/shared/controls/SMarker.css +16 -16
  12. package/src/components/shared/controls/SMarker.vue +27 -1
  13. package/src/components/shared/data-display/SActionListItem.css +2 -2
  14. package/src/components/shared/data-display/SActionListItem.vue +8 -1
  15. package/src/components/shared/data-display/SMessage.css +8 -8
  16. package/src/components/shared/data-display/SMessage.vue +8 -1
  17. package/src/components/shared/data-display/SProgressBar.css +12 -12
  18. package/src/components/shared/data-display/SProgressBar.vue +12 -6
  19. package/src/components/shared/data-display/SProgressIndicator.css +5 -5
  20. package/src/components/shared/data-display/SProgressIndicator.vue +9 -1
  21. package/src/components/shared/data-display/SStatus.css +10 -10
  22. package/src/components/shared/data-display/SStatus.vue +9 -1
  23. package/src/components/shared/data-display/STable.vue +27 -4
  24. package/src/components/shared/data-display/STooltip.css +4 -4
  25. package/src/components/shared/data-display/STooltip.vue +159 -29
  26. package/src/components/shared/data-display/STooltipTarget.css +26 -0
  27. package/src/components/shared/data-display/STooltipTarget.vue +94 -0
  28. package/src/components/shared/navigation/STabs.vue +11 -3
  29. package/src/internal/ownedAttrs.ts +3 -0
  30. package/src/internal/pillSurface.ts +4 -3
  31. package/src/internal/semanticTone.ts +23 -0
  32. package/src/styles/style.css +7 -7
package/README.md CHANGED
@@ -159,6 +159,16 @@ Multi-column sorting uses controlled `v-model:sorts`: the consumer sets
159
159
  adds a descriptor, changes its direction, or removes it; the visible number is
160
160
  the priority. The table never reorders rows on its own.
161
161
 
162
+ Последовательные действия заголовков формируют внутренний intent draft до
163
+ подтверждения controlled prop. Consumer может применить `sort`/`sorts` через
164
+ store или асинхронный render-cycle: второй критерий не теряет первый. Любое
165
+ authoritative обновление prop становится новой базой следующих действий.
166
+
167
+ Sequential header actions update an internal intent draft until the controlled
168
+ prop is acknowledged. Consumers may apply `sort` or `sorts` through a store or
169
+ an asynchronous render cycle without losing an earlier criterion. Any
170
+ authoritative prop update becomes the base for subsequent actions.
171
+
162
172
  ```vue
163
173
  <STable
164
174
  v-model:sorts="sorts"
@@ -170,6 +180,31 @@ the priority. The table never reorders rows on its own.
170
180
  />
171
181
  ```
172
182
 
183
+ ### Подсказка на части действия / Tooltip for part of an action
184
+
185
+ Обычный tooltip использует combined `trigger`. Для пассивного фрагмента внутри
186
+ уже интерактивного leaf используйте split bindings: `focusOwner` остаётся на
187
+ кнопке и владеет ARIA/focus, а `pointerTarget` получает пассивный
188
+ `STooltipTarget`. Компонент target не создаёт `role` или `tabindex`.
189
+
190
+ A regular tooltip uses the combined `trigger`. For a passive fragment inside an
191
+ existing interactive leaf, use split bindings: `focusOwner` remains on the
192
+ button and owns ARIA/focus, while `pointerTarget` is applied to a passive
193
+ `STooltipTarget`. The target component adds neither a role nor a tab stop.
194
+
195
+ ```vue
196
+ <STooltip text="Открывает палитру команд" v-slot="{ focusOwner, pointerTarget }">
197
+ <SButton v-bind="focusOwner" width="full" content-align="between">
198
+ Открыть палитру команд
199
+ <STooltipTarget
200
+ v-bind="pointerTarget"
201
+ as="kbd"
202
+ appearance="keycap"
203
+ >⌘K</STooltipTarget>
204
+ </SButton>
205
+ </STooltip>
206
+ ```
207
+
173
208
  ## Темы и стили / Themes and styles
174
209
 
175
210
  Для обычного приложения подключайте полный style entrypoint один раз:
@@ -220,6 +255,24 @@ Components consume semantic custom properties. Consumers override documented
220
255
  tokens on the root theme container and do not depend on internal CSS classes or
221
256
  template structure.
222
257
 
258
+ Floating surfaces teleport to `document.body`. If the host application uses an
259
+ overlay scale above the UI-kit defaults, align the public layer tokens on
260
+ `:root`, preserving `--s-layer-floating < --s-layer-toast <
261
+ --s-layer-overlay-backdrop`:
262
+
263
+ ```css
264
+ :root {
265
+ --host-overlay-surface: 2400;
266
+ --s-layer-floating: calc(var(--host-overlay-surface) + 10);
267
+ --s-layer-toast: calc(var(--host-overlay-surface) + 20);
268
+ --s-layer-overlay-backdrop: calc(var(--host-overlay-surface) + 100);
269
+ }
270
+ ```
271
+
272
+ `--host-overlay-surface` belongs to the host application. The complete layer
273
+ integration contract and verification checklist are published in
274
+ `docs/theming.md` inside the npm package.
275
+
223
276
  ## Публичный контракт / Public contract
224
277
 
225
278
  `package.json#exports` — источник истины для импортов. Публичный контракт
@@ -244,6 +297,10 @@ import { themeVariants } from '@pgcorp/ui-kit/theme'
244
297
  Do not style internal DOM through `:deep` or private selectors.
245
298
  - Выражайте состояние через documented props, slots, variants и tokens.
246
299
  Express state through documented props, slots, variants, and tokens.
300
+ - Используйте собственные `data-*` как test/tour hooks; UI-kit-owned `data-s-*`
301
+ являются внутренней presentation boundary и не являются selector API.
302
+ Use consumer-owned `data-*` for test and tour hooks; UI-kit-owned `data-s-*`
303
+ are an internal presentation boundary, not a selector API.
247
304
  - Передавайте человекочитаемые labels интерактивным компонентам.
248
305
  Provide human-readable labels to interactive components.
249
306
 
@@ -18,6 +18,9 @@
18
18
 
19
19
  Компонентный contract выражается через typed props, slots, events и exposed
20
20
  methods. Внутренний DOM, CSS-классы и private modules не являются API.
21
+ UI-kit-owned атрибуты `data-s-*` относятся к внутренней presentation boundary и
22
+ не являются стабильными consumer selectors. Произвольные consumer-owned
23
+ `data-*` остаются поддерживаемыми test/tour hooks canonical owner.
21
24
  Публичные source-SFC объявляют runtime props и events локально: consumer Vue
22
25
  compiler регистрирует их без межфайлового filesystem type resolver.
23
26
  Публикуемый production-source компилируется с `target/lib: ES2020`, совпадающим
@@ -41,6 +44,9 @@ Primary groups:
41
44
 
42
45
  Component contracts are expressed through typed props, slots, events, and exposed
43
46
  methods. Internal DOM, CSS classes, and private modules are not API.
47
+ UI-kit-owned `data-s-*` attributes belong to the internal presentation boundary
48
+ and are not stable consumer selectors. Arbitrary consumer-owned `data-*`
49
+ attributes remain supported test and tour hooks on the canonical owner.
44
50
  Public source SFCs declare runtime props and events locally, so the consumer Vue
45
51
  compiler registers them without a cross-file filesystem type resolver.
46
52
  Published production source compiles with the `target/lib: ES2020` baseline from
package/docs/theming.md CHANGED
@@ -17,6 +17,35 @@ applyThemeIdToDocument(document, 'classic-light')
17
17
  theme-контейнере. Не стилизуйте внутренний DOM компонентов через `:deep`,
18
18
  private selectors или зависимость от структуры template.
19
19
 
20
+ ### Интеграция шкалы слоёв host-приложения
21
+
22
+ Floating surfaces (`STooltip`, меню, select и popover) телепортируются в
23
+ `document.body`, поэтому их layer tokens разрешаются в корневом scope документа,
24
+ а не во вложенном контейнере компонента. Базовая шкала UI-kit сохраняет порядок
25
+ `floating < toast < overlay-backdrop`:
26
+
27
+ - `--s-layer-floating: 40` — tooltip, menu, select и popover;
28
+ - `--s-layer-toast: 50` — уведомления;
29
+ - `--s-layer-overlay-backdrop: 11000` — backdrop первого modal/drawer;
30
+ - `--s-layer-overlay-surface` вычисляется из backdrop и surface offset.
31
+
32
+ Если host-приложение использует другую систему overlay-слоёв, задайте UI-kit
33
+ tokens на `:root` относительно наибольшего top-level слоя host. Не меняйте
34
+ `z-index` внутренних элементов компонентов и не привязывайтесь к их CSS-классам.
35
+
36
+ ```css
37
+ :root {
38
+ --host-overlay-surface: 2400;
39
+ --s-layer-floating: calc(var(--host-overlay-surface) + 10);
40
+ --s-layer-toast: calc(var(--host-overlay-surface) + 20);
41
+ --s-layer-overlay-backdrop: calc(var(--host-overlay-surface) + 100);
42
+ }
43
+ ```
44
+
45
+ Значение `--host-overlay-surface` в примере является контрактом host-приложения,
46
+ а не фиксированным значением UI-kit. После интеграции проверьте tooltip/menu,
47
+ toast и modal/drawer внутри и снаружи host overlay.
48
+
20
49
  ## English
21
50
 
22
51
  `tokens.css` contains primitive and semantic tokens. `style.css` includes tokens,
@@ -33,3 +62,32 @@ applyThemeIdToDocument(document, 'classic-light')
33
62
  Override documented CSS custom properties on the root theme container only. Do
34
63
  not style component internals through `:deep`, private selectors, or template
35
64
  structure dependencies.
65
+
66
+ ### Host application layer-scale integration
67
+
68
+ Floating surfaces (`STooltip`, menus, selects, and popovers) teleport to
69
+ `document.body`. Their layer tokens therefore resolve in the document root scope,
70
+ not in a nested component container. The default UI-kit scale preserves
71
+ `floating < toast < overlay-backdrop`:
72
+
73
+ - `--s-layer-floating: 40` — tooltips, menus, selects, and popovers;
74
+ - `--s-layer-toast: 50` — notifications;
75
+ - `--s-layer-overlay-backdrop: 11000` — the first modal/drawer backdrop;
76
+ - `--s-layer-overlay-surface` is derived from the backdrop and surface offset.
77
+
78
+ When the host application uses another overlay scale, define the UI-kit tokens
79
+ on `:root` relative to the highest top-level host layer. Do not change component
80
+ internal `z-index` values or depend on internal CSS classes.
81
+
82
+ ```css
83
+ :root {
84
+ --host-overlay-surface: 2400;
85
+ --s-layer-floating: calc(var(--host-overlay-surface) + 10);
86
+ --s-layer-toast: calc(var(--host-overlay-surface) + 20);
87
+ --s-layer-overlay-backdrop: calc(var(--host-overlay-surface) + 100);
88
+ }
89
+ ```
90
+
91
+ The `--host-overlay-surface` value is a host-application contract, not a fixed
92
+ UI-kit value. Verify tooltip/menu, toast, and modal/drawer behavior both inside
93
+ and outside host overlays after integration.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgcorp/ui-kit",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Typed Vue 3 design system with accessible components, semantic themes, and workbench patterns.",
5
5
  "type": "module",
6
6
  "types": "./index.ts",
@@ -348,6 +348,9 @@
348
348
  "shared/data-display/STooltipAnchor.vue": [
349
349
  "src/components/shared/data-display/STooltipAnchor.vue"
350
350
  ],
351
+ "shared/data-display/STooltipTarget.vue": [
352
+ "src/components/shared/data-display/STooltipTarget.vue"
353
+ ],
351
354
  "shared/data-display/SVirtualList": [
352
355
  "src/components/shared/data-display/SVirtualList.ts"
353
356
  ],
@@ -524,6 +527,7 @@
524
527
  "./shared/data-display/SToastContainer.vue": "./src/components/shared/data-display/SToastContainer.vue",
525
528
  "./shared/data-display/STooltip.vue": "./src/components/shared/data-display/STooltip.vue",
526
529
  "./shared/data-display/STooltipAnchor.vue": "./src/components/shared/data-display/STooltipAnchor.vue",
530
+ "./shared/data-display/STooltipTarget.vue": "./src/components/shared/data-display/STooltipTarget.vue",
527
531
  "./shared/data-display/SVirtualList": "./src/components/shared/data-display/SVirtualList.ts",
528
532
  "./shared/data-display/SVirtualList.vue": "./src/components/shared/data-display/SVirtualList.vue",
529
533
  "./shared/database/SDataGrid.vue": "./src/components/shared/database/SDataGrid.vue",
@@ -33,33 +33,33 @@
33
33
  }
34
34
 
35
35
  /* Severity: info */
36
- .s-toast[data-severity='info'] {
36
+ .s-toast[data-s-tone='info'] {
37
37
  box-shadow: var(--s-toast-shadow-info);
38
38
  }
39
- .s-toast[data-severity='info'] .s-toast-icon {
39
+ .s-toast[data-s-tone='info'] .s-toast-icon {
40
40
  @apply text-info-500;
41
41
  }
42
42
 
43
43
  /* Severity: success */
44
- .s-toast[data-severity='success'] {
44
+ .s-toast[data-s-tone='success'] {
45
45
  box-shadow: var(--s-toast-shadow-success);
46
46
  }
47
- .s-toast[data-severity='success'] .s-toast-icon {
47
+ .s-toast[data-s-tone='success'] .s-toast-icon {
48
48
  @apply text-success-500;
49
49
  }
50
50
 
51
51
  /* Severity: warn */
52
- .s-toast[data-severity='warn'] {
52
+ .s-toast[data-s-tone='warn'] {
53
53
  box-shadow: var(--s-toast-shadow-warn);
54
54
  }
55
- .s-toast[data-severity='warn'] .s-toast-icon {
55
+ .s-toast[data-s-tone='warn'] .s-toast-icon {
56
56
  @apply text-warn-500;
57
57
  }
58
58
 
59
59
  /* Severity: danger */
60
- .s-toast[data-severity='danger'] {
60
+ .s-toast[data-s-tone='danger'] {
61
61
  box-shadow: var(--s-toast-shadow-danger);
62
62
  }
63
- .s-toast[data-severity='danger'] .s-toast-icon {
63
+ .s-toast[data-s-tone='danger'] .s-toast-icon {
64
64
  @apply text-danger-500;
65
65
  }
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div
3
+ :data-s-tone="toneBinding['data-s-tone']"
3
4
  class="s-toast"
4
- :data-severity="notification.severity"
5
5
  :role="liveAttributes.role"
6
6
  :aria-live="liveAttributes.ariaLive"
7
7
  :aria-atomic="liveAttributes.ariaAtomic"
@@ -40,6 +40,7 @@ import { computed } from 'vue'
40
40
 
41
41
  import { X } from '../../icons/sputnigUiIcons'
42
42
  import { resolveFeedbackLive, resolveFeedbackPresentation } from '../../../internal/feedbackPresentation'
43
+ import { resolveSemanticToneBinding } from '../../../internal/semanticTone'
43
44
  import type { Notification } from '../../../stores/useNotifierStore';
44
45
  import SButton from '../controls/SButton.vue'
45
46
 
@@ -53,6 +54,12 @@ const emit = defineEmits<{
53
54
  dismiss: [id: string]
54
55
  }>()
55
56
  const presentation = computed(() => resolveFeedbackPresentation(props.notification.severity))
57
+ const toneBinding = computed(() => resolveSemanticToneBinding(
58
+ 'SToastItem',
59
+ 'notification.severity',
60
+ props.notification.severity,
61
+ ['info', 'success', 'warn', 'danger'] as const,
62
+ ))
56
63
  const liveAttributes = computed(() => resolveFeedbackLive(props.notification.severity, 'auto'))
57
64
  </script>
58
65
  <style lang="postcss" src="./SToastItem.css" scoped></style>
@@ -78,7 +78,7 @@
78
78
  .s-button[data-icon-only='true'][data-size='md'] { width: var(--s-control-hitbox-md); height: var(--s-control-hitbox-md); }
79
79
  .s-button[data-icon-only='true'][data-size='lg'] { width: var(--s-control-hitbox-lg); height: var(--s-control-hitbox-lg); }
80
80
 
81
- .s-button[data-severity='secondary'] {
81
+ .s-button[data-s-tone='secondary'] {
82
82
  --s-button-accent: var(--s-button-secondary-accent);
83
83
  --s-button-solid-background: var(--s-button-secondary-solid-background);
84
84
  --s-button-solid-background-hover: var(--s-button-secondary-solid-background-hover);
@@ -88,7 +88,7 @@
88
88
  --s-button-outline: var(--color-surface-300);
89
89
  }
90
90
 
91
- .s-button[data-severity='success'] {
91
+ .s-button[data-s-tone='success'] {
92
92
  --s-button-accent: var(--s-button-success-accent);
93
93
  --s-button-solid-background: var(--s-button-success-solid-background);
94
94
  --s-button-solid-background-hover: var(--s-button-success-solid-background-hover);
@@ -97,7 +97,7 @@
97
97
  --s-button-foreground: var(--s-button-success-foreground);
98
98
  }
99
99
 
100
- .s-button[data-severity='info'] {
100
+ .s-button[data-s-tone='info'] {
101
101
  --s-button-accent: var(--s-button-info-accent);
102
102
  --s-button-solid-background: var(--s-button-info-solid-background);
103
103
  --s-button-solid-background-hover: var(--s-button-info-solid-background-hover);
@@ -106,7 +106,7 @@
106
106
  --s-button-foreground: var(--s-button-info-foreground);
107
107
  }
108
108
 
109
- .s-button[data-severity='warn'] {
109
+ .s-button[data-s-tone='warn'] {
110
110
  --s-button-accent: var(--s-button-warn-accent);
111
111
  --s-button-solid-background: var(--s-button-warn-solid-background);
112
112
  --s-button-solid-background-hover: var(--s-button-warn-solid-background-hover);
@@ -115,7 +115,7 @@
115
115
  --s-button-foreground: var(--s-button-warn-foreground);
116
116
  }
117
117
 
118
- .s-button[data-severity='danger'] {
118
+ .s-button[data-s-tone='danger'] {
119
119
  --s-button-accent: var(--s-button-danger-accent);
120
120
  --s-button-solid-background: var(--s-button-danger-solid-background);
121
121
  --s-button-solid-background-hover: var(--s-button-danger-solid-background-hover);
@@ -124,7 +124,7 @@
124
124
  --s-button-foreground: var(--s-button-danger-foreground);
125
125
  }
126
126
 
127
- .s-button[data-severity='contrast'] {
127
+ .s-button[data-s-tone='contrast'] {
128
128
  --s-button-accent: var(--s-button-contrast-accent);
129
129
  --s-button-solid-background: var(--s-button-contrast-solid-background);
130
130
  --s-button-solid-background-hover: var(--s-button-contrast-solid-background-hover);
@@ -3,8 +3,8 @@
3
3
  :is="rootComponent"
4
4
  :ref="setRootRef"
5
5
  v-bind="rootBindings"
6
+ :data-s-tone="toneBinding['data-s-tone']"
6
7
  class="s-button"
7
- :data-severity="severity"
8
8
  :data-size="size"
9
9
  :data-width="width"
10
10
  :data-content-align="contentAlign"
@@ -63,6 +63,7 @@ import { ChevronRight } from '../../icons/sputnigUiIcons'
63
63
  import type { InteractiveElementApi } from '../../../internal/interactiveElement'
64
64
  import { resolveLinkTarget } from '../../../internal/linkTarget'
65
65
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
66
+ import { resolveSemanticToneBinding } from '../../../internal/semanticTone'
66
67
  import {
67
68
  PassiveContentBoundary,
68
69
  useInteractiveLeafRegistration,
@@ -79,6 +80,7 @@ import type {
79
80
  defineOptions({ inheritAttrs: false })
80
81
 
81
82
  export type SButtonSeverity = 'primary' | 'secondary' | 'success' | 'info' | 'warn' | 'danger' | 'contrast'
83
+ const BUTTON_SEVERITIES = ['primary', 'secondary', 'success', 'info', 'warn', 'danger', 'contrast'] as const satisfies readonly SButtonSeverity[]
82
84
  export type SButtonSize = 'xs' | 'sm' | 'md' | 'lg'
83
85
  export type SButtonIconSize = 'default' | 'large'
84
86
  export type SButtonDensity = 'default' | 'compact'
@@ -186,6 +188,12 @@ const defaultContentComponent = (): ReturnType<NonNullable<typeof buttonSlots.de
186
188
  )
187
189
  const rootRef = ref<HTMLButtonElement | HTMLAnchorElement | null>(null)
188
190
  const ownedAttrs = useOwnedAttrs({ component: 'SButton', owner: 'native button' })
191
+ const toneBinding = computed(() => resolveSemanticToneBinding(
192
+ 'SButton',
193
+ 'severity',
194
+ props.severity,
195
+ BUTTON_SEVERITIES,
196
+ ))
189
197
  const isRegisteredTreeAction = useTreeActionRegistration({
190
198
  owner: 'SButton',
191
199
  element: rootRef,
@@ -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)
@@ -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"
@@ -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',