@ds-mo/ui 2.11.1 → 2.12.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 (226) hide show
  1. package/README.md +13 -13
  2. package/dist/.build-stamp +1 -1
  3. package/dist/components/ds-app-shell.js +1 -1
  4. package/dist/components/ds-banner.js +1 -1
  5. package/dist/components/ds-banner.js.map +1 -1
  6. package/dist/components/ds-bar-nav.js +1 -1
  7. package/dist/components/ds-bar-nav.js.map +1 -1
  8. package/dist/components/ds-button-filled.js +1 -1
  9. package/dist/components/{ds-accordion.d.ts → ds-button-unfilled.d.ts} +4 -4
  10. package/dist/components/ds-button-unfilled.js +2 -0
  11. package/dist/components/ds-card-data-viz.js +1 -1
  12. package/dist/components/ds-card-data-viz.js.map +1 -1
  13. package/dist/components/ds-card-setting.js +1 -1
  14. package/dist/components/ds-card-setting.js.map +1 -1
  15. package/dist/components/ds-chart-bar.js +1 -1
  16. package/dist/components/ds-chart-bar.js.map +1 -1
  17. package/dist/components/ds-chart-donut.js +1 -1
  18. package/dist/components/ds-chart-donut.js.map +1 -1
  19. package/dist/components/ds-chart-legend.js +1 -1
  20. package/dist/components/ds-chart-legend.js.map +1 -1
  21. package/dist/components/ds-chart-line.js +1 -1
  22. package/dist/components/ds-checkbox.js +1 -1
  23. package/dist/components/ds-checkbox.js.map +1 -1
  24. package/dist/components/ds-chip.js +1 -1
  25. package/dist/components/ds-chip.js.map +1 -1
  26. package/dist/components/ds-divider.js +1 -1
  27. package/dist/components/ds-empty-state.js +1 -1
  28. package/dist/components/ds-empty-state.js.map +1 -1
  29. package/dist/components/ds-field.js +1 -1
  30. package/dist/components/ds-field.js.map +1 -1
  31. package/dist/components/ds-header.js +1 -1
  32. package/dist/components/ds-header.js.map +1 -1
  33. package/dist/components/ds-icon.js +1 -1
  34. package/dist/components/ds-input.js +1 -1
  35. package/dist/components/ds-input.js.map +1 -1
  36. package/dist/components/ds-loader.js +1 -1
  37. package/dist/components/ds-loader.js.map +1 -1
  38. package/dist/components/ds-menu.js +1 -1
  39. package/dist/components/ds-modal.js +1 -1
  40. package/dist/components/ds-pagination.js +1 -1
  41. package/dist/components/ds-pagination.js.map +1 -1
  42. package/dist/components/ds-panel-nav.js +1 -1
  43. package/dist/components/ds-panel-nav.js.map +1 -1
  44. package/dist/components/ds-panel-tools.js +1 -1
  45. package/dist/components/ds-panel-tools.js.map +1 -1
  46. package/dist/components/ds-radio-group.js +1 -1
  47. package/dist/components/ds-radio-group.js.map +1 -1
  48. package/dist/components/ds-scrollbar.js +1 -1
  49. package/dist/components/ds-select.js +1 -1
  50. package/dist/components/ds-select.js.map +1 -1
  51. package/dist/components/ds-shell-gradient-picker.js +1 -1
  52. package/dist/components/ds-shell-gradient-swatch.js +1 -1
  53. package/dist/components/ds-skeleton.js +1 -1
  54. package/dist/components/ds-skeleton.js.map +1 -1
  55. package/dist/components/ds-slider.js +1 -1
  56. package/dist/components/ds-slider.js.map +1 -1
  57. package/dist/components/ds-tab-group-nav.js +1 -1
  58. package/dist/components/ds-tab-group.js +1 -1
  59. package/dist/components/ds-tab-group.js.map +1 -1
  60. package/dist/components/ds-table.js +1 -1
  61. package/dist/components/ds-table.js.map +1 -1
  62. package/dist/components/ds-tag.js +1 -1
  63. package/dist/components/ds-tag.js.map +1 -1
  64. package/dist/components/ds-text.js +1 -1
  65. package/dist/components/ds-toggle.js +1 -1
  66. package/dist/components/ds-toggle.js.map +1 -1
  67. package/dist/components/ds-tooltip-data-viz.js +1 -1
  68. package/dist/components/ds-tooltip-data-viz.js.map +1 -1
  69. package/dist/components/ds-tooltip.js +1 -1
  70. package/dist/components/ds-tooltip.js.map +1 -1
  71. package/dist/components/p-B9dHCgY-.js +2 -0
  72. package/dist/components/p-B9dHCgY-.js.map +1 -0
  73. package/dist/components/p-BPK76IsW.js +2 -0
  74. package/dist/components/p-BPK76IsW.js.map +1 -0
  75. package/dist/components/p-BQq26pt9.js.map +1 -1
  76. package/dist/components/p-BTC9Njj6.js +2 -0
  77. package/dist/components/p-BTC9Njj6.js.map +1 -0
  78. package/dist/components/p-BZIOTcnM.js +2 -0
  79. package/dist/components/p-BZIOTcnM.js.map +1 -0
  80. package/dist/components/{p-DdFUTZ3y.js → p-C3ORQUOp.js} +2 -2
  81. package/dist/components/{p-8Nt92j1-.js → p-CyFVn2Sc.js} +2 -2
  82. package/dist/components/p-DKdI8T8v.js +2 -0
  83. package/dist/components/p-DKdI8T8v.js.map +1 -0
  84. package/dist/components/{p-DOU4cbpc.js → p-DRqxVEye.js} +2 -2
  85. package/dist/components/p-DkcH8mRl.js +2 -0
  86. package/dist/components/p-DkcH8mRl.js.map +1 -0
  87. package/dist/components/p-DusYu-if.js +2 -0
  88. package/dist/components/p-DusYu-if.js.map +1 -0
  89. package/dist/components/p-EK1hOP19.js.map +1 -1
  90. package/dist/components/p-UbKTMQjv.js +2 -0
  91. package/dist/components/p-UbKTMQjv.js.map +1 -0
  92. package/dist/lib/nav/index.js +3 -0
  93. package/dist/lib/nav/index.js.map +2 -2
  94. package/dist/lib/utils/index.js +6 -0
  95. package/dist/lib/utils/index.js.map +2 -2
  96. package/dist/lib/utils/token-defaults.d.ts +3 -0
  97. package/dist/types/components/ButtonFilled/ButtonFilled.d.ts +17 -3
  98. package/dist/types/components/ButtonFilled/index.d.ts +1 -1
  99. package/dist/types/components/ButtonUnfilled/ButtonUnfilled.d.ts +66 -0
  100. package/dist/types/components/ButtonUnfilled/index.d.ts +2 -0
  101. package/dist/types/components/CardDataViz/CardDataViz.d.ts +5 -1
  102. package/dist/types/components/CardSetting/CardSetting.d.ts +1 -1
  103. package/dist/types/components/ChartDonut/ChartDonut.d.ts +7 -0
  104. package/dist/types/components/Checkbox/Checkbox.d.ts +1 -1
  105. package/dist/types/components/Chip/Chip.d.ts +18 -14
  106. package/dist/types/components/Chip/index.d.ts +1 -1
  107. package/dist/types/components/Input/Input.d.ts +1 -1
  108. package/dist/types/components/Pagination/Pagination.d.ts +1 -1
  109. package/dist/types/components/PanelNav/PanelNav.d.ts +2 -0
  110. package/dist/types/components/PanelTools/panel-tools-types.d.ts +1 -1
  111. package/dist/types/components/RadioGroup/RadioGroup.d.ts +2 -2
  112. package/dist/types/components/Select/Select.d.ts +2 -2
  113. package/dist/types/components/ShellGradientSwatch/ShellGradientSwatch.d.ts +1 -1
  114. package/dist/types/components/Slider/Slider.d.ts +1 -1
  115. package/dist/types/components/TabGroup/TabGroup.d.ts +1 -1
  116. package/dist/types/components/TabGroup/tab-item-utils.d.ts +2 -1
  117. package/dist/types/components/TabGroupNav/TabGroupNav.d.ts +1 -1
  118. package/dist/types/components/Tag/Tag.d.ts +0 -2
  119. package/dist/types/components/Tag/index.d.ts +1 -1
  120. package/dist/types/components/Toggle/Toggle.d.ts +1 -1
  121. package/dist/types/components/Tooltip/Tooltip.d.ts +25 -6
  122. package/dist/types/components/TooltipDataViz/TooltipDataViz.d.ts +18 -0
  123. package/dist/types/components.d.ts +277 -476
  124. package/dist/types/utils/token-defaults.d.ts +3 -0
  125. package/package.json +6 -3
  126. package/src/angular/index.ts +1 -6
  127. package/src/angular/proxies.ts +33 -170
  128. package/src/react/components.ts +1 -6
  129. package/src/react/ds-button-unfilled.ts +33 -0
  130. package/src/react/ds-chip.ts +2 -10
  131. package/src/wc/components/BarNav/BarNav.tsx +3 -2
  132. package/src/wc/components/BarNav/bar-nav-tabs-menu-utils.ts +1 -1
  133. package/src/wc/components/ButtonFilled/ButtonFilled.tsx +90 -11
  134. package/src/wc/components/ButtonFilled/index.ts +6 -1
  135. package/src/wc/components/ButtonUnfilled/ButtonUnfilled.tsx +223 -0
  136. package/src/wc/components/ButtonUnfilled/index.ts +6 -0
  137. package/src/wc/components/CardDataViz/CardDataViz.tsx +36 -2
  138. package/src/wc/components/CardSetting/CardSetting.tsx +4 -6
  139. package/src/wc/components/ChartDonut/ChartDonut.tsx +50 -3
  140. package/src/wc/components/ChartLegend/ChartLegend.tsx +3 -0
  141. package/src/wc/components/Checkbox/Checkbox.tsx +5 -5
  142. package/src/wc/components/Chip/Chip.tsx +50 -67
  143. package/src/wc/components/Chip/index.ts +1 -1
  144. package/src/wc/components/Input/Input.tsx +4 -4
  145. package/src/wc/components/Menu/Menu.tsx +7 -4
  146. package/src/wc/components/Pagination/Pagination.tsx +6 -6
  147. package/src/wc/components/PanelNav/PanelNav.tsx +77 -48
  148. package/src/wc/components/PanelTools/PanelTools.tsx +19 -16
  149. package/src/wc/components/PanelTools/panel-tools-types.ts +1 -1
  150. package/src/wc/components/PanelTools/panel-tools-utils.ts +1 -1
  151. package/src/wc/components/RadioGroup/RadioGroup.tsx +7 -7
  152. package/src/wc/components/Select/Select.tsx +5 -5
  153. package/src/wc/components/ShellGradientSwatch/ShellGradientSwatch.tsx +4 -3
  154. package/src/wc/components/Slider/Slider.tsx +7 -3
  155. package/src/wc/components/TabGroup/TabGroup.tsx +13 -7
  156. package/src/wc/components/TabGroup/tab-item-utils.ts +2 -1
  157. package/src/wc/components/TabGroupNav/TabGroupNav.tsx +14 -13
  158. package/src/wc/components/Tag/Tag.tsx +25 -24
  159. package/src/wc/components/Tag/index.ts +1 -1
  160. package/src/wc/components/Toggle/Toggle.tsx +6 -6
  161. package/src/wc/components/Tooltip/Tooltip.tsx +166 -64
  162. package/src/wc/components/TooltipDataViz/TooltipDataViz.tsx +57 -2
  163. package/src/wc/components.d.ts +277 -476
  164. package/src/wc/utils/control-density.css +42 -0
  165. package/src/wc/utils/control-inactive.css +20 -0
  166. package/src/wc/utils/interaction-fill.css +162 -0
  167. package/src/wc/utils/scroll-edge-fade.css +5 -0
  168. package/src/wc/utils/token-defaults.ts +6 -0
  169. package/dist/components/ds-accordion.js +0 -2
  170. package/dist/components/ds-accordion.js.map +0 -1
  171. package/dist/components/ds-breadcrumb.d.ts +0 -11
  172. package/dist/components/ds-breadcrumb.js +0 -2
  173. package/dist/components/ds-breadcrumb.js.map +0 -1
  174. package/dist/components/ds-button-unfilled-icon.d.ts +0 -11
  175. package/dist/components/ds-button-unfilled-icon.js +0 -2
  176. package/dist/components/ds-fade.d.ts +0 -11
  177. package/dist/components/ds-fade.js +0 -2
  178. package/dist/components/ds-fade.js.map +0 -1
  179. package/dist/components/ds-surface.d.ts +0 -11
  180. package/dist/components/ds-surface.js +0 -2
  181. package/dist/components/ds-surface.js.map +0 -1
  182. package/dist/components/ds-toast-provider.d.ts +0 -11
  183. package/dist/components/ds-toast-provider.js +0 -2
  184. package/dist/components/ds-toast-provider.js.map +0 -1
  185. package/dist/components/p-BMQbEIrQ.js +0 -2
  186. package/dist/components/p-BMQbEIrQ.js.map +0 -1
  187. package/dist/components/p-BSFgy5r6.js +0 -2
  188. package/dist/components/p-BSFgy5r6.js.map +0 -1
  189. package/dist/components/p-CBUIn8dk.js +0 -2
  190. package/dist/components/p-CBUIn8dk.js.map +0 -1
  191. package/dist/components/p-ChVwCVHy.js +0 -2
  192. package/dist/components/p-ChVwCVHy.js.map +0 -1
  193. package/dist/components/p-CpNIQXz6.js +0 -2
  194. package/dist/components/p-CpNIQXz6.js.map +0 -1
  195. package/dist/components/p-CvgHQfvT.js +0 -2
  196. package/dist/components/p-CvgHQfvT.js.map +0 -1
  197. package/dist/components/p-bDf6yBQd.js +0 -2
  198. package/dist/components/p-bDf6yBQd.js.map +0 -1
  199. package/dist/types/components/Accordion/Accordion.d.ts +0 -28
  200. package/dist/types/components/Breadcrumb/Breadcrumb.d.ts +0 -16
  201. package/dist/types/components/ButtonUnfilledIcon/ButtonUnfilledIcon.d.ts +0 -45
  202. package/dist/types/components/ButtonUnfilledIcon/index.d.ts +0 -2
  203. package/dist/types/components/Fade/Fade.d.ts +0 -22
  204. package/dist/types/components/Fade/index.d.ts +0 -2
  205. package/dist/types/components/Surface/Surface.d.ts +0 -22
  206. package/dist/types/components/Toast/Toast.d.ts +0 -16
  207. package/dist/types/components/Toast/toast-service.d.ts +0 -24
  208. package/src/react/ds-accordion.ts +0 -27
  209. package/src/react/ds-breadcrumb.ts +0 -27
  210. package/src/react/ds-button-unfilled-icon.ts +0 -33
  211. package/src/react/ds-fade.ts +0 -26
  212. package/src/react/ds-surface.ts +0 -26
  213. package/src/react/ds-toast-provider.ts +0 -26
  214. package/src/wc/components/Accordion/Accordion.tsx +0 -144
  215. package/src/wc/components/Breadcrumb/Breadcrumb.tsx +0 -61
  216. package/src/wc/components/ButtonUnfilledIcon/ButtonUnfilledIcon.tsx +0 -129
  217. package/src/wc/components/ButtonUnfilledIcon/index.ts +0 -1
  218. package/src/wc/components/Fade/Fade.tsx +0 -58
  219. package/src/wc/components/Fade/index.ts +0 -1
  220. package/src/wc/components/Surface/Surface.tsx +0 -81
  221. package/src/wc/components/Toast/Toast.tsx +0 -179
  222. package/src/wc/components/Toast/toast-service.ts +0 -67
  223. /package/dist/components/{ds-button-unfilled-icon.js.map → ds-button-unfilled.js.map} +0 -0
  224. /package/dist/components/{p-DdFUTZ3y.js.map → p-C3ORQUOp.js.map} +0 -0
  225. /package/dist/components/{p-8Nt92j1-.js.map → p-CyFVn2Sc.js.map} +0 -0
  226. /package/dist/components/{p-DOU4cbpc.js.map → p-DRqxVEye.js.map} +0 -0
@@ -13,6 +13,27 @@ export type ButtonFilledIntent =
13
13
 
14
14
  export type ButtonFilledContrast = 'bold' | 'strong' | 'medium' | 'faint';
15
15
 
16
+ export type ButtonFilledVariant = 'icon' | 'label' | 'icon-label';
17
+
18
+ export type ButtonFilledSize = 'md' | 'sm' | 'xs';
19
+
20
+ /** Emphasis text per control-density size (buttons use emphasis, unlike Tag). */
21
+ const TEXT_VARIANT: Record<ButtonFilledSize, string> = {
22
+ md: 'text-body-medium-emphasis',
23
+ sm: 'text-body-small-emphasis',
24
+ xs: 'text-caption-emphasis',
25
+ };
26
+
27
+ /**
28
+ * `ds-icon` size prop matching control-density icon metrics
29
+ * (md→20 / sm→16 / xs→12 via `--dimension-iconography-*`).
30
+ */
31
+ const ICON_SIZE: Record<ButtonFilledSize, 'md' | 'sm' | 'xs'> = {
32
+ md: 'md',
33
+ sm: 'sm',
34
+ xs: 'xs',
35
+ };
36
+
16
37
  @Component({
17
38
  tag: 'ds-button-filled',
18
39
  styleUrl: 'ButtonFilled.css',
@@ -21,7 +42,19 @@ export type ButtonFilledContrast = 'bold' | 'strong' | 'medium' | 'faint';
21
42
  export class ButtonFilled {
22
43
  @Element() el!: HTMLElement;
23
44
 
24
- /** Icon name passed to <ds-icon>. */
45
+ /**
46
+ * Content layout. Default is label-only; pass `icon` for icon-only chrome
47
+ * (nav / tool rails) or `icon-label` for leading icon + text.
48
+ */
49
+ @Prop() variant: ButtonFilledVariant = 'label';
50
+
51
+ /** Control density (height, padding, icon, type). */
52
+ @Prop() size: ButtonFilledSize = 'md';
53
+
54
+ /** Visible text for `label` / `icon-label` variants. */
55
+ @Prop() label: string = '';
56
+
57
+ /** Icon name passed to <ds-icon> for `icon` / `icon-label` variants. */
25
58
  @Prop() icon: string = '';
26
59
 
27
60
  /** Semantic colour intent. */
@@ -34,12 +67,12 @@ export class ButtonFilled {
34
67
  @Prop() contrast: ButtonFilledContrast = 'bold';
35
68
 
36
69
  /** Disables interaction. */
37
- @Prop() inactive: boolean = false;
70
+ @Prop() isInactive: boolean = false;
38
71
 
39
72
  /** Native button type. */
40
73
  @Prop() type: 'button' | 'submit' | 'reset' = 'button';
41
74
 
42
- @Prop({ attribute: 'aria-label' }) ariaLabel: string = 'action';
75
+ @Prop({ attribute: 'aria-label' }) ariaLabel: string | undefined;
43
76
 
44
77
  @Event() dsClick!: EventEmitter<MouseEvent>;
45
78
 
@@ -51,34 +84,80 @@ export class ButtonFilled {
51
84
  }
52
85
 
53
86
  private handleClick = (event: MouseEvent) => {
54
- if (this.inactive) return;
87
+ if (this.isInactive) return;
55
88
  this.dsClick.emit(event);
56
89
  };
57
90
 
91
+ private get showIcon(): boolean {
92
+ return this.variant === 'icon' || this.variant === 'icon-label';
93
+ }
94
+
95
+ private get showLabel(): boolean {
96
+ return this.variant === 'label' || this.variant === 'icon-label';
97
+ }
98
+
99
+ private get accessibleName(): string | undefined {
100
+ if (this.ariaLabel) return this.ariaLabel;
101
+ if (this.showLabel && this.label) return undefined;
102
+ if (this.variant === 'icon') return this.icon || 'action';
103
+ return this.label || 'action';
104
+ }
105
+
58
106
  render() {
107
+ const textVariant = TEXT_VARIANT[this.size];
108
+ const iconSize = ICON_SIZE[this.size];
109
+
59
110
  const cls: Record<string, boolean> = {
60
111
  'button-filled': true,
61
112
  'ds-focus-ring-inset': true,
62
- 'button-filled--inactive': this.inactive,
113
+ 'ds-interaction-fill': !this.isInactive,
114
+ /* Bold is the default filled contrast — on-bold interaction tokens. */
115
+ 'ds-interaction-fill--on-bold': this.contrast === 'bold',
116
+ 'ds-interaction-fill--on-strong': this.contrast === 'strong',
117
+ 'ds-interaction-fill--on-medium': this.contrast === 'medium',
118
+ /* faint → default app interaction tokens (no --on-*). */
119
+ 'ds-control-inactive': this.isInactive,
120
+ 'ds-control--md': this.size === 'md',
121
+ 'ds-control--sm': this.size === 'sm',
122
+ 'ds-control--xs': this.size === 'xs',
123
+ 'button-filled--icon': this.variant === 'icon',
124
+ 'button-filled--label': this.variant === 'label',
125
+ 'button-filled--icon-label': this.variant === 'icon-label',
63
126
  [`button-filled--intent-${this.intent}`]: true,
64
127
  [`button-filled--contrast-${this.contrast}`]: this.contrast !== 'bold',
65
128
  };
66
129
 
67
130
  return (
68
- <Host tabIndex={-1}>
131
+ <Host
132
+ class={{
133
+ 'button-filled-host': true,
134
+ 'button-filled-host--icon': this.variant === 'icon',
135
+ 'ds-control--md': this.size === 'md',
136
+ 'ds-control--sm': this.size === 'sm',
137
+ 'ds-control--xs': this.size === 'xs',
138
+ }}
139
+ tabIndex={-1}
140
+ >
69
141
  <button
70
142
  ref={el => {
71
143
  this.buttonEl = el ?? null;
72
144
  }}
73
145
  type={this.type}
74
146
  class={cls}
75
- disabled={this.inactive}
76
- aria-label={this.ariaLabel}
147
+ disabled={this.isInactive}
148
+ aria-label={this.accessibleName}
77
149
  onClick={this.handleClick}
78
150
  >
79
- <span class="button-filled__icon-wrap">
80
- <ds-icon name={this.icon} size="md" color="inherit" />
81
- </span>
151
+ {this.showIcon && (
152
+ <span class="button-filled__icon-wrap ds-interaction-fill__content">
153
+ <ds-icon name={this.icon} size={iconSize} color="inherit" />
154
+ </span>
155
+ )}
156
+ {this.showLabel && (
157
+ <span class={{ 'button-filled__label': true, [textVariant]: true, 'ds-interaction-fill__content': true }}>
158
+ {this.label}
159
+ </span>
160
+ )}
82
161
  </button>
83
162
  </Host>
84
163
  );
@@ -1 +1,6 @@
1
- export type { ButtonFilledContrast, ButtonFilledIntent } from './ButtonFilled';
1
+ export type {
2
+ ButtonFilledContrast,
3
+ ButtonFilledIntent,
4
+ ButtonFilledSize,
5
+ ButtonFilledVariant,
6
+ } from './ButtonFilled';
@@ -0,0 +1,223 @@
1
+ import { Component, Element, Event, EventEmitter, h, Host, Method, Prop } from '@stencil/core';
2
+
3
+ export type ButtonUnfilledOnBackgroundContrast = 'default' | 'medium' | 'bold' | 'strong';
4
+
5
+ export type ButtonUnfilledBackground = 'always-dark' | 'navigation';
6
+
7
+ export type ButtonUnfilledVariant = 'icon' | 'label' | 'icon-label';
8
+
9
+ export type ButtonUnfilledSize = 'md' | 'sm' | 'xs';
10
+
11
+ /** Emphasis text per control-density size (buttons use emphasis, unlike Tag). */
12
+ const TEXT_VARIANT: Record<ButtonUnfilledSize, string> = {
13
+ md: 'text-body-medium-emphasis',
14
+ sm: 'text-body-small-emphasis',
15
+ xs: 'text-caption-emphasis',
16
+ };
17
+
18
+ /**
19
+ * `ds-icon` size prop matching control-density icon metrics
20
+ * (md→20 / sm→16 / xs→12 via `--dimension-iconography-*`).
21
+ */
22
+ const ICON_SIZE: Record<ButtonUnfilledSize, 'md' | 'sm' | 'xs'> = {
23
+ md: 'md',
24
+ sm: 'sm',
25
+ xs: 'xs',
26
+ };
27
+
28
+ @Component({
29
+ tag: 'ds-button-unfilled',
30
+ styleUrl: 'ButtonUnfilled.css',
31
+ scoped: true,
32
+ })
33
+ export class ButtonUnfilled {
34
+ @Element() el!: HTMLElement;
35
+
36
+ /**
37
+ * Content layout. Default is label-only; pass `icon` for icon-only chrome
38
+ * (nav / tool rails) or `icon-label` for leading icon + text.
39
+ */
40
+ @Prop() variant: ButtonUnfilledVariant = 'label';
41
+
42
+ /** Control density (height, padding, icon, type). */
43
+ @Prop() size: ButtonUnfilledSize = 'md';
44
+
45
+ /** Visible text for `label` / `icon-label` variants. */
46
+ @Prop() label: string = '';
47
+
48
+ /** Icon name passed to <ds-icon> for `icon` / `icon-label` variants. */
49
+ @Prop() icon: string = '';
50
+
51
+ /** Active/selected visual state. */
52
+ @Prop() isActive: boolean = false;
53
+
54
+ /**
55
+ * When active, render the selected interaction fill.
56
+ * Default `true` for general UI. Shell chrome (nav / tool rails) should pass
57
+ * `false` so selection is foreground-only.
58
+ */
59
+ @Prop() activeFill: boolean = true;
60
+
61
+ /** Show a 1px tertiary inset border. Default on; shell chrome can pass `false`. */
62
+ @Prop() hasBorder: boolean = true;
63
+
64
+ /** Show a notification dot at the top-right of the icon zone (icon variant only). */
65
+ @Prop() dot: boolean = false;
66
+
67
+ /** Disables interaction. */
68
+ @Prop() isInactive: boolean = false;
69
+
70
+ /** Native button type. */
71
+ @Prop() type: 'button' | 'submit' | 'reset' = 'button';
72
+
73
+ /**
74
+ * Foreground and interaction tokens when the button sits on a contrasting parent
75
+ * background (default, medium, bold, or strong).
76
+ */
77
+ @Prop({ attribute: 'on-background-contrast' })
78
+ backgroundContrast?: ButtonUnfilledOnBackgroundContrast;
79
+
80
+ /** Parent surface context for navigation and always-dark chrome. */
81
+ @Prop() background: ButtonUnfilledBackground | undefined;
82
+
83
+ @Prop({ attribute: 'aria-label' }) ariaLabel: string | undefined;
84
+ @Prop() controls: string | undefined;
85
+ @Prop() expanded: boolean | undefined;
86
+ @Prop() haspopup: string | undefined;
87
+ @Prop() pressed: boolean | undefined;
88
+
89
+ /**
90
+ * Native `tabindex` for roving keyboard groups in shell chrome.
91
+ * Omit for the default button tab stop (`0`).
92
+ */
93
+ @Prop({ attribute: 'tab-index' }) focusTabIndex?: number;
94
+
95
+ @Event() dsClick!: EventEmitter<MouseEvent>;
96
+ @Event() dsChange!: EventEmitter<boolean>;
97
+
98
+ private buttonEl: HTMLButtonElement | null = null;
99
+
100
+ @Method()
101
+ async setFocus() {
102
+ this.buttonEl?.focus();
103
+ }
104
+
105
+ private handleClick = (event: MouseEvent) => {
106
+ if (this.isInactive) return;
107
+ this.dsClick.emit(event);
108
+ this.dsChange.emit(!this.isActive);
109
+ };
110
+
111
+ private effectiveContrast(): ButtonUnfilledOnBackgroundContrast {
112
+ return this.backgroundContrast ?? 'default';
113
+ }
114
+
115
+ private get showIcon(): boolean {
116
+ return this.variant === 'icon' || this.variant === 'icon-label';
117
+ }
118
+
119
+ private get showLabel(): boolean {
120
+ return this.variant === 'label' || this.variant === 'icon-label';
121
+ }
122
+
123
+ private get showDot(): boolean {
124
+ return this.variant === 'icon' && this.dot;
125
+ }
126
+
127
+ /** Knock-out ring: selected fill → active wash; otherwise surface token. */
128
+ private get dotRing(): string {
129
+ if (this.isActive && this.activeFill) {
130
+ return 'var(--ds-interaction-active)';
131
+ }
132
+ return 'var(--ds-button-unfilled-dot-ring)';
133
+ }
134
+
135
+ private get accessibleName(): string | undefined {
136
+ if (this.ariaLabel) return this.ariaLabel;
137
+ if (this.showLabel && this.label) return undefined;
138
+ if (this.variant === 'icon') return this.icon || 'action';
139
+ return this.label || 'action';
140
+ }
141
+
142
+ render() {
143
+ const bg = this.background;
144
+ const contrast = this.effectiveContrast();
145
+ const textVariant = TEXT_VARIANT[this.size];
146
+ const iconSize = ICON_SIZE[this.size];
147
+
148
+ const cls: Record<string, boolean> = {
149
+ 'button-unfilled': true,
150
+ 'ds-focus-ring-inset': true,
151
+ 'ds-interaction-fill': true,
152
+ 'ds-interaction-fill--selected': this.isActive && this.activeFill && !this.isInactive,
153
+ 'ds-interaction-fill--on-medium': contrast === 'medium',
154
+ 'ds-interaction-fill--on-bold': contrast === 'bold',
155
+ 'ds-interaction-fill--on-strong': contrast === 'strong',
156
+ 'ds-interaction-fill--on-always-dark': bg === 'always-dark',
157
+ 'ds-interaction-fill--on-navigation': bg === 'navigation',
158
+ 'button-unfilled--active': this.isActive,
159
+ 'button-unfilled--bordered': this.hasBorder,
160
+ 'ds-control-inactive': this.isInactive,
161
+ 'ds-control--md': this.size === 'md',
162
+ 'ds-control--sm': this.size === 'sm',
163
+ 'ds-control--xs': this.size === 'xs',
164
+ 'button-unfilled--icon': this.variant === 'icon',
165
+ 'button-unfilled--label': this.variant === 'label',
166
+ 'button-unfilled--icon-label': this.variant === 'icon-label',
167
+ 'button-unfilled--contrast-medium': contrast === 'medium',
168
+ 'button-unfilled--contrast-bold': contrast === 'bold',
169
+ 'button-unfilled--contrast-strong': contrast === 'strong',
170
+ 'button-unfilled--on-always-dark': bg === 'always-dark',
171
+ 'button-unfilled--on-navigation': bg === 'navigation',
172
+ };
173
+
174
+ return (
175
+ <Host
176
+ class={{
177
+ 'button-unfilled-host': true,
178
+ 'button-unfilled-host--icon': this.variant === 'icon',
179
+ 'ds-control--md': this.size === 'md',
180
+ 'ds-control--sm': this.size === 'sm',
181
+ 'ds-control--xs': this.size === 'xs',
182
+ }}
183
+ tabIndex={-1}
184
+ >
185
+ <button
186
+ ref={el => {
187
+ this.buttonEl = el ?? null;
188
+ }}
189
+ type={this.type}
190
+ class={cls}
191
+ disabled={this.isInactive}
192
+ tabIndex={this.focusTabIndex ?? 0}
193
+ aria-label={this.accessibleName}
194
+ aria-controls={this.controls}
195
+ aria-expanded={this.expanded === undefined ? undefined : String(this.expanded)}
196
+ aria-haspopup={this.haspopup}
197
+ aria-pressed={this.pressed === undefined ? undefined : String(this.pressed)}
198
+ onClick={this.handleClick}
199
+ >
200
+ {this.showIcon && (
201
+ <span class="button-unfilled__icon-wrap ds-interaction-fill__content">
202
+ <ds-icon name={this.icon} size={iconSize} color="inherit" />
203
+ {this.showDot && (
204
+ <ds-badge
205
+ class="button-unfilled__dot"
206
+ variant="dot"
207
+ background={this.dotRing}
208
+ label=""
209
+ aria-hidden="true"
210
+ />
211
+ )}
212
+ </span>
213
+ )}
214
+ {this.showLabel && (
215
+ <span class={{ 'button-unfilled__label': true, [textVariant]: true, 'ds-interaction-fill__content': true }}>
216
+ {this.label}
217
+ </span>
218
+ )}
219
+ </button>
220
+ </Host>
221
+ );
222
+ }
223
+ }
@@ -0,0 +1,6 @@
1
+ export type {
2
+ ButtonUnfilledBackground,
3
+ ButtonUnfilledOnBackgroundContrast,
4
+ ButtonUnfilledSize,
5
+ ButtonUnfilledVariant,
6
+ } from './ButtonUnfilled';
@@ -1,9 +1,14 @@
1
1
  import { Component, Element, State, h, Host, Prop } from '@stencil/core';
2
2
 
3
- export type CardDataVizWidth = 'xs' | 'sm' | 'md' | 'lg';
3
+ export type CardDataVizWidth = 'sm' | 'md' | 'lg';
4
+
5
+ /** Matches the `dsSliceHover`/`dsItemHover` detail shape emitted by `ds-chart-*` and `ds-chart-legend`. */
6
+ type HoveredDatum = { label: string } | null;
7
+
8
+ /** A slotted chart/legend that opts into hover sync by exposing an `activeLabel` prop. */
9
+ type SyncableSlot = HTMLElement & { activeLabel?: string | null };
4
10
 
5
11
  const CARD_WIDTH_VARS: Record<CardDataVizWidth, string> = {
6
- xs: 'var(--dimension-card-width-xs)',
7
12
  sm: 'var(--dimension-card-width-sm)',
8
13
  md: 'var(--dimension-card-width-md)',
9
14
  lg: 'var(--dimension-card-width-lg)',
@@ -39,6 +44,35 @@ export class CardDataViz {
39
44
  this.hasLegendSlot = !!this.el.querySelector(':scope > [slot="legend"]');
40
45
  }
41
46
 
47
+ componentDidLoad() {
48
+ // Sync the chart's and legend's `activeLabel` from each other's hover events, so any
49
+ // ds-chart-* + ds-chart-legend pair cross-highlights without every consumer having to
50
+ // wire it up by hand. Delegated on the host (rather than bound to the specific slotted
51
+ // elements) since Stencil events bubble and the slot content can be swapped at runtime.
52
+ this.el.addEventListener('dsSliceHover', this.handleChartHover);
53
+ this.el.addEventListener('dsItemHover', this.handleLegendHover);
54
+ }
55
+
56
+ disconnectedCallback() {
57
+ this.el.removeEventListener('dsSliceHover', this.handleChartHover);
58
+ this.el.removeEventListener('dsItemHover', this.handleLegendHover);
59
+ }
60
+
61
+ private handleChartHover = (e: Event) => {
62
+ // Not `:scope >` — Stencil's slot polyfill for scoped (non-shadow) components relocates
63
+ // slotted children into an inner `.chart-region`/`.legend-region` wrapper on render, so
64
+ // they're no longer direct children of the host by the time this fires.
65
+ const legend = this.el.querySelector('[slot="legend"]') as SyncableSlot | null;
66
+ if (!legend || !('activeLabel' in legend)) return;
67
+ legend.activeLabel = (e as CustomEvent<HoveredDatum>).detail?.label ?? null;
68
+ };
69
+
70
+ private handleLegendHover = (e: Event) => {
71
+ const chart = this.el.querySelector('[slot="chart"]') as SyncableSlot | null;
72
+ if (!chart || !('activeLabel' in chart)) return;
73
+ chart.activeLabel = (e as CustomEvent<HoveredDatum>).detail?.label ?? null;
74
+ };
75
+
42
76
  render() {
43
77
  return (
44
78
  <Host class="card-data-viz" style={{ '--_card-data-viz-width': CARD_WIDTH_VARS[this.cardWidth] }}>
@@ -1,9 +1,8 @@
1
1
  import { Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core';
2
2
 
3
- export type CardSettingWidth = 'xs' | 'sm' | 'md' | 'lg';
3
+ export type CardSettingWidth = 'sm' | 'md' | 'lg';
4
4
 
5
5
  const CARD_WIDTH_VARS: Record<CardSettingWidth, string> = {
6
- xs: 'var(--dimension-card-width-xs)',
7
6
  sm: 'var(--dimension-card-width-sm)',
8
7
  md: 'var(--dimension-card-width-md)',
9
8
  lg: 'var(--dimension-card-width-lg)',
@@ -60,7 +59,7 @@ export class CardSetting {
60
59
  </ds-text>
61
60
  </div>
62
61
  {!editing ? (
63
- <ds-button-unfilled-icon
62
+ <ds-button-unfilled variant="icon"
64
63
  type="button"
65
64
  icon="Pencil"
66
65
  aria-label="Edit"
@@ -68,15 +67,14 @@ export class CardSetting {
68
67
  />
69
68
  ) : (
70
69
  <div class="card-setting__actions">
71
- <ds-button-unfilled-icon
70
+ <ds-button-unfilled variant="icon"
72
71
  type="button"
73
72
  icon="Cross"
74
73
  backgroundContrast="bold"
75
- has-border
76
74
  aria-label="Cancel"
77
75
  onDsClick={this.exitEdit}
78
76
  />
79
- <ds-button-filled
77
+ <ds-button-filled variant="icon"
80
78
  type="button"
81
79
  icon="Check"
82
80
  intent="brand"
@@ -10,6 +10,12 @@ const CENTER_GAP_PX = 0;
10
10
  /** Fraction of the inner-circle diameter center text is allowed to use before truncating. */
11
11
  const CENTER_TEXT_SAFE_WIDTH_RATIO = 0.82;
12
12
 
13
+ type DonutTooltipState = {
14
+ datum: ChartDatum;
15
+ x: number;
16
+ y: number;
17
+ };
18
+
13
19
  @Component({
14
20
  tag: 'ds-chart-donut',
15
21
  styleUrl: 'ChartDonut.css',
@@ -33,10 +39,14 @@ export class ChartDonut {
33
39
  * Externally controlled highlight, matched by `label` — e.g. drive this from a sibling
34
40
  * `ds-chart-legend`'s `dsItemHover` event to keep chart and legend hover in sync.
35
41
  * Falls back to this component's own pointer/focus hover when unset.
42
+ * External sync dims slices only — the data-viz tooltip is reserved for this
43
+ * component's own pointer/keyboard interaction.
36
44
  */
37
45
  @Prop() activeLabel: string | null = null;
38
46
 
39
47
  @State() private hoveredLabel: string | null = null;
48
+ /** Own-hover tooltip (cursor-following). Cleared on leave; not driven by `activeLabel`. */
49
+ @State() private tooltip: DonutTooltipState | null = null;
40
50
  /** Center text, truncated with an ellipsis if it doesn't fit inside the inner circle. */
41
51
  @State() private displayedValueText: string = '';
42
52
  @State() private displayedCaptionText: string = '';
@@ -46,10 +56,30 @@ export class ChartDonut {
46
56
 
47
57
  private valueTextEl?: SVGTextElement;
48
58
  private captionTextEl?: SVGTextElement;
59
+ private wrapperEl?: HTMLDivElement;
49
60
 
50
- private handleHover(datum: ChartDatum | null) {
61
+ private handleHover(datum: ChartDatum | null, event?: MouseEvent) {
51
62
  this.hoveredLabel = datum?.label ?? null;
52
63
  this.dsSliceHover.emit(datum);
64
+ if (!datum) {
65
+ this.tooltip = null;
66
+ return;
67
+ }
68
+ this.updateTooltip(datum, event);
69
+ }
70
+
71
+ private handleSliceMove(datum: ChartDatum, event: MouseEvent) {
72
+ if (this.hoveredLabel !== datum.label) return;
73
+ this.updateTooltip(datum, event);
74
+ }
75
+
76
+ private updateTooltip(datum: ChartDatum, event?: MouseEvent) {
77
+ const wrapper = this.wrapperEl;
78
+ if (!wrapper) return;
79
+ const rect = wrapper.getBoundingClientRect();
80
+ const x = event ? event.clientX - rect.left : this.size / 2;
81
+ const y = event ? event.clientY - rect.top : this.size / 2;
82
+ this.tooltip = { datum, x, y };
53
83
  }
54
84
 
55
85
  componentDidRender() {
@@ -115,10 +145,17 @@ export class ChartDonut {
115
145
  // Show the full text before the post-render truncation pass has measured it (avoids a blank first paint).
116
146
  const fullValueText = this.centerValue ?? formatCompactNumber(total);
117
147
  const fullCaptionText = this.centerCaption ?? '';
148
+ const tip = this.tooltip;
118
149
 
119
150
  return (
120
151
  <Host class="chart-donut">
121
- <div class="chart-donut__wrapper" style={{ width: `${this.size}px`, height: `${this.size}px` }}>
152
+ <div
153
+ class="chart-donut__wrapper"
154
+ style={{ width: `${this.size}px`, height: `${this.size}px` }}
155
+ ref={el => {
156
+ this.wrapperEl = (el as HTMLDivElement) ?? undefined;
157
+ }}
158
+ >
122
159
  <svg
123
160
  class="chart-donut__svg"
124
161
  viewBox={`0 0 ${this.size} ${this.size}`}
@@ -141,7 +178,8 @@ export class ChartDonut {
141
178
  tabindex={0}
142
179
  role="img"
143
180
  aria-label={`${datum.label}: ${datum.value}`}
144
- onMouseEnter={() => this.handleHover(datum)}
181
+ onMouseEnter={(e: MouseEvent) => this.handleHover(datum, e)}
182
+ onMouseMove={(e: MouseEvent) => this.handleSliceMove(datum, e)}
145
183
  onMouseLeave={() => this.handleHover(null)}
146
184
  onFocus={() => this.handleHover(datum)}
147
185
  onBlur={() => this.handleHover(null)}
@@ -177,6 +215,15 @@ export class ChartDonut {
177
215
  </text>
178
216
  )}
179
217
  </svg>
218
+ {tip && (
219
+ <ds-tooltip-data-viz
220
+ key={tip.datum.label}
221
+ label={tip.datum.label}
222
+ value={formatCompactNumber(tip.datum.value)}
223
+ x={tip.x}
224
+ y={tip.y}
225
+ />
226
+ )}
180
227
  </div>
181
228
  </Host>
182
229
  );
@@ -73,6 +73,9 @@ export class ChartLegend {
73
73
  class={{
74
74
  'chart-legend__item': true,
75
75
  'chart-legend__item--interactive': !!item.href,
76
+ 'ds-control--md': true,
77
+ 'ds-interaction-fill': true,
78
+ 'ds-focus-ring-inset': !!item.href,
76
79
  }}
77
80
  style={{ opacity: isDimmed ? String(DIMMED_OPACITY) : '1' }}
78
81
  href={item.href}
@@ -13,12 +13,12 @@ export class Checkbox {
13
13
  @Prop() label!: string;
14
14
  @Prop({ mutable: true }) checked: boolean = false;
15
15
  @Prop() indeterminate: boolean = false;
16
- @Prop() inactive: boolean = false;
16
+ @Prop() isInactive: boolean = false;
17
17
 
18
18
  @Event() dsChange!: EventEmitter<boolean>;
19
19
 
20
20
  private handleActivate = () => {
21
- if (this.inactive) return;
21
+ if (this.isInactive) return;
22
22
  this.checked = !this.checked;
23
23
  this.dsChange.emit(this.checked);
24
24
  };
@@ -37,10 +37,10 @@ export class Checkbox {
37
37
  <Host
38
38
  role="checkbox"
39
39
  aria-checked={this.indeterminate ? 'mixed' : String(this.checked)}
40
- aria-disabled={this.inactive || undefined}
40
+ aria-disabled={this.isInactive || undefined}
41
41
  aria-labelledby={this.labelId}
42
- tabIndex={this.inactive ? -1 : 0}
43
- class={{ checkbox: true, inactive: this.inactive }}
42
+ tabIndex={this.isInactive ? -1 : 0}
43
+ class={{ checkbox: true, 'ds-control-inactive': this.isInactive }}
44
44
  onClick={this.handleActivate}
45
45
  onKeyDown={this.handleKeyDown}
46
46
  >