@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
@@ -0,0 +1,26 @@
1
+ @reference "../../../styles/reference.css";
2
+
3
+ .s-tooltip-target {
4
+ @apply inline-flex min-w-0 shrink-0 items-center justify-center align-middle;
5
+ }
6
+
7
+ .s-tooltip-target[data-appearance='keycap'] {
8
+ @apply border border-surface-300 bg-surface-100 font-mono font-semibold text-surface-700 shadow-sm;
9
+ border-radius: var(--radius-xs);
10
+ }
11
+
12
+ .s-tooltip-target[data-appearance='keycap'][data-size='xs'] {
13
+ @apply min-h-4 min-w-4 px-1 text-[0.625rem] leading-none;
14
+ }
15
+
16
+ .s-tooltip-target[data-appearance='keycap'][data-size='sm'] {
17
+ @apply min-h-5 min-w-5 px-1.5 text-xs leading-none;
18
+ }
19
+
20
+ .s-tooltip-target[data-appearance='keycap'][data-size='md'] {
21
+ @apply min-h-6 min-w-6 px-2 text-sm leading-none;
22
+ }
23
+
24
+ :global(.dark .s-tooltip-target[data-appearance='keycap']) {
25
+ @apply border-surface-600 bg-surface-800 text-surface-200;
26
+ }
@@ -0,0 +1,94 @@
1
+ <template>
2
+ <component
3
+ :is="rootTag"
4
+ ref="targetRef"
5
+ v-bind="ownedAttrs.bindings()"
6
+ class="s-tooltip-target"
7
+ :data-appearance="resolvedAppearance"
8
+ :data-size="resolvedSize"
9
+ @pointerenter="emit('pointerenter', $event)"
10
+ @pointerleave="emit('pointerleave', $event)"
11
+ >
12
+ <slot />
13
+ </component>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import { computed, ref, type VNodeChild } from 'vue'
18
+ import type { ElementApi } from '../../../internal/interactiveElement'
19
+ import { useOwnedAttrs } from '../../../internal/ownedAttrs'
20
+ import { validateExactString } from '../../../internal/runtimeContract'
21
+
22
+ defineOptions({
23
+ name: 'STooltipTarget',
24
+ inheritAttrs: false,
25
+ })
26
+
27
+ export type STooltipTargetTag = 'span' | 'kbd'
28
+ export type STooltipTargetAppearance = 'plain' | 'keycap'
29
+ export type STooltipTargetSize = 'xs' | 'sm' | 'md'
30
+ export type STooltipTargetApi = ElementApi<HTMLElement>
31
+
32
+ /**
33
+ * Пассивная pointer/geometry target для split-контракта STooltip.
34
+ * Passive pointer and geometry target for the STooltip split contract.
35
+ */
36
+ export interface Props {
37
+ /** Семантический HTML root без собственной интерактивности. / Semantic HTML root without its own interactivity. */
38
+ as?: STooltipTargetTag
39
+ /** Нейтральная или keycap presentation. / Plain or keycap presentation. */
40
+ appearance?: STooltipTargetAppearance
41
+ /** Централизованная visual density target. / Centralized visual density of the target. */
42
+ size?: STooltipTargetSize
43
+ }
44
+
45
+ const props = withDefaults(defineProps<Props>(), {
46
+ as: 'span',
47
+ appearance: 'plain',
48
+ size: 'sm',
49
+ })
50
+ const emit = defineEmits<{
51
+ pointerenter: [event: PointerEvent]
52
+ pointerleave: [event: PointerEvent]
53
+ }>()
54
+ const slots = defineSlots<{
55
+ /** Пассивное содержимое target. / Passive target content. */
56
+ default: () => VNodeChild
57
+ }>()
58
+
59
+ if (!slots.default) {
60
+ throw new Error('STooltipTarget: default slot обязателен. / STooltipTarget: the default slot is required.')
61
+ }
62
+
63
+ const targetRef = ref<HTMLElement | null>(null)
64
+ const ownedAttrs = useOwnedAttrs({ component: 'STooltipTarget', owner: 'passive tooltip target' })
65
+ const resolvedAs = computed(() => validateExactString(
66
+ 'STooltipTarget',
67
+ 'as',
68
+ props.as,
69
+ ['span', 'kbd'] as const,
70
+ ))
71
+ const rootTag = computed(() => resolvedAs.value === 'kbd' ? 'kbd' : 'span')
72
+ const resolvedAppearance = computed(() => validateExactString(
73
+ 'STooltipTarget',
74
+ 'appearance',
75
+ props.appearance,
76
+ ['plain', 'keycap'] as const,
77
+ ))
78
+ const resolvedSize = computed(() => validateExactString(
79
+ 'STooltipTarget',
80
+ 'size',
81
+ props.size,
82
+ ['xs', 'sm', 'md'] as const,
83
+ ))
84
+
85
+ const getElement = (): HTMLElement => {
86
+ const element = targetRef.value
87
+ if (!element) throw new Error('STooltipTarget: native target element is unavailable')
88
+ return element
89
+ }
90
+
91
+ defineExpose<STooltipTargetApi>({ getElement })
92
+ </script>
93
+
94
+ <style lang="postcss" src="./STooltipTarget.css" scoped></style>
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <div
3
3
  v-bind="ownedAttrs.bindings()"
4
+ :data-s-tone="toneBinding['data-s-tone']"
4
5
  class="s-tabs"
5
6
  :data-testid="testId"
6
7
  :data-composition="composition"
@@ -9,7 +10,6 @@
9
10
  :data-density="tabDensity"
10
11
  :data-sizing="tabSizing"
11
12
  :data-list-width="tabListWidth"
12
- :data-tone="tone"
13
13
  :data-close-mode="closeButtonMode"
14
14
  :data-close-visibility="closeButtonVisibility"
15
15
  :data-radius="surfaceRadius"
@@ -58,6 +58,7 @@ import {
58
58
  type VNodeChild,
59
59
  } from 'vue'
60
60
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
61
+ import { resolveSemanticToneBinding } from '../../../internal/semanticTone'
61
62
  import { lastItem } from '../../../internal/es2020'
62
63
  import {
63
64
  tabsContextKey,
@@ -80,6 +81,7 @@ export type STabsComposition = 'managed' | 'custom'
80
81
  export type STabsVariant = 'default' | 'flat' | 'bottom' | 'icon-only' | 'icon-with-label'
81
82
  export type STabsActivationMode = 'automatic' | 'manual'
82
83
  export type STabsTone = 'primary' | 'success'
84
+ const TABS_TONES = ['primary', 'success'] as const satisfies readonly STabsTone[]
83
85
  export type STabsSizing =
84
86
  | 'content'
85
87
  | 'compact'
@@ -176,6 +178,12 @@ const slots = defineSlots<{
176
178
  controls?: () => VNodeChild
177
179
  }>()
178
180
  const ownedAttrs = useOwnedAttrs({ component: 'STabs', owner: 'tabs state and layout root' })
181
+ const toneBinding = computed(() => resolveSemanticToneBinding(
182
+ 'STabs',
183
+ 'tone',
184
+ props.tone,
185
+ TABS_TONES,
186
+ ))
179
187
 
180
188
  function assertStableKey(value: unknown, coordinate: string): asserts value is string {
181
189
  if (typeof value !== 'string' || !value.trim() || value !== value.trim()) {
@@ -569,12 +577,12 @@ defineExpose({
569
577
  --s-tab-selected-border-color: var(--color-primary-400);
570
578
  --s-tab-disabled-color: var(--color-surface-600);
571
579
  }
572
- .s-tabs[data-tone='success'] {
580
+ .s-tabs[data-s-tone='success'] {
573
581
  --s-tab-hover-color: var(--color-success-700);
574
582
  --s-tab-selected-color: var(--color-success-700);
575
583
  --s-tab-selected-border-color: var(--color-success-600);
576
584
  }
577
- :global(.dark) .s-tabs[data-tone='success'] {
585
+ :global(.dark) .s-tabs[data-s-tone='success'] {
578
586
  --s-tab-hover-color: var(--color-success-300);
579
587
  --s-tab-selected-color: var(--color-success-300);
580
588
  --s-tab-selected-border-color: var(--color-success-400);
@@ -294,6 +294,9 @@ export const ownedAttributeSchemas = Object.freeze({
294
294
  'data-testid': 'data',
295
295
  id: 'string',
296
296
  }),
297
+ STooltipTarget: defineOwnedAttributeSchema({
298
+ 'data-testid': 'data',
299
+ }),
297
300
  SVirtualList: defineOwnedAttributeSchema({}),
298
301
  SAsyncState: defineOwnedAttributeSchema({}),
299
302
  SDataGrid: defineOwnedAttributeSchema({}),
@@ -2,6 +2,7 @@ import {
2
2
  validateBoolean,
3
3
  validateExactString,
4
4
  } from './runtimeContract'
5
+ import { resolveSemanticToneBinding } from './semanticTone'
5
6
 
6
7
  const PILL_KINDS = ['badge', 'chip'] as const
7
8
  const PILL_SEVERITIES = [
@@ -43,7 +44,7 @@ export interface PillSurfaceContract {
43
44
 
44
45
  export interface PillSurfaceBindings {
45
46
  'data-s-pill-surface': PillSurfaceKind
46
- 'data-s-feedback-tone': PillSurfaceSeverity
47
+ 'data-s-tone': PillSurfaceSeverity
47
48
  'data-s-pill-size': PillSurfaceSize
48
49
  'data-s-pill-dot'?: 'true'
49
50
  'data-s-pill-closable'?: 'true'
@@ -62,7 +63,7 @@ export function resolvePillSurfaceBindings(
62
63
  contract: PillSurfaceContract,
63
64
  ): PillSurfaceBindings {
64
65
  const kind = validateExactString(owner, 'pill kind', contract.kind, PILL_KINDS)
65
- const severity = validateExactString(owner, 'severity', contract.severity, PILL_SEVERITIES)
66
+ const toneBinding = resolveSemanticToneBinding(owner, 'severity', contract.severity, PILL_SEVERITIES)
66
67
  const size = validateExactString(owner, 'size', contract.size, PILL_SIZES)
67
68
  const maxWidth = validateExactString(owner, 'maxWidth', contract.maxWidth, PILL_MAX_WIDTHS)
68
69
  const iconMotion = validateExactString(owner, 'iconMotion', contract.iconMotion, PILL_ICON_MOTIONS)
@@ -80,7 +81,7 @@ export function resolvePillSurfaceBindings(
80
81
 
81
82
  return {
82
83
  'data-s-pill-surface': kind,
83
- 'data-s-feedback-tone': severity,
84
+ ...toneBinding,
84
85
  'data-s-pill-size': size,
85
86
  ...(dot ? { 'data-s-pill-dot': 'true' as const } : {}),
86
87
  ...(closable ? { 'data-s-pill-closable': 'true' as const } : {}),
@@ -0,0 +1,23 @@
1
+ import { validateExactString } from './runtimeContract'
2
+
3
+ /** Единственный internal DOM marker для component-owned visual tone. */
4
+ export const SEMANTIC_TONE_ATTRIBUTE = 'data-s-tone' as const
5
+
6
+ export type SemanticToneBinding<Tone extends string> = Readonly<{
7
+ 'data-s-tone': Tone
8
+ }>
9
+
10
+ /**
11
+ * Проверяет component-specific tone vocabulary и создаёт единый namespaced DOM binding.
12
+ * Validates a component-specific tone vocabulary and creates the canonical namespaced DOM binding.
13
+ */
14
+ export function resolveSemanticToneBinding<const Tones extends readonly string[]>(
15
+ owner: string,
16
+ coordinate: string,
17
+ value: unknown,
18
+ tones: Tones,
19
+ ): SemanticToneBinding<Tones[number]> {
20
+ return {
21
+ [SEMANTIC_TONE_ATTRIBUTE]: validateExactString(owner, coordinate, value, tones),
22
+ }
23
+ }
@@ -26,37 +26,37 @@ body {
26
26
  }
27
27
 
28
28
  @layer components {
29
- :where([data-s-feedback-tone="primary"]) {
29
+ :where([data-s-tone="primary"]) {
30
30
  --s-feedback-tone-background: var(--s-feedback-primary-background);
31
31
  --s-feedback-tone-foreground: var(--s-feedback-primary-foreground);
32
32
  }
33
33
 
34
- :where([data-s-feedback-tone="secondary"]) {
34
+ :where([data-s-tone="secondary"]) {
35
35
  --s-feedback-tone-background: var(--s-feedback-secondary-background);
36
36
  --s-feedback-tone-foreground: var(--s-feedback-secondary-foreground);
37
37
  }
38
38
 
39
- :where([data-s-feedback-tone="success"]) {
39
+ :where([data-s-tone="success"]) {
40
40
  --s-feedback-tone-background: var(--s-feedback-success-background);
41
41
  --s-feedback-tone-foreground: var(--s-feedback-success-foreground);
42
42
  }
43
43
 
44
- :where([data-s-feedback-tone="info"]) {
44
+ :where([data-s-tone="info"]) {
45
45
  --s-feedback-tone-background: var(--s-feedback-info-background);
46
46
  --s-feedback-tone-foreground: var(--s-feedback-info-foreground);
47
47
  }
48
48
 
49
- :where([data-s-feedback-tone="warn"]) {
49
+ :where([data-s-tone="warn"]) {
50
50
  --s-feedback-tone-background: var(--s-feedback-warn-background);
51
51
  --s-feedback-tone-foreground: var(--s-feedback-warn-foreground);
52
52
  }
53
53
 
54
- :where([data-s-feedback-tone="danger"]) {
54
+ :where([data-s-tone="danger"]) {
55
55
  --s-feedback-tone-background: var(--s-feedback-danger-background);
56
56
  --s-feedback-tone-foreground: var(--s-feedback-danger-foreground);
57
57
  }
58
58
 
59
- :where([data-s-feedback-tone="contrast"]) {
59
+ :where([data-s-tone="contrast"]) {
60
60
  --s-feedback-tone-background: var(--s-feedback-contrast-background);
61
61
  --s-feedback-tone-foreground: var(--s-feedback-contrast-foreground);
62
62
  }