@ds-mo/ui 1.1.0 → 1.3.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 (107) hide show
  1. package/README.md +82 -3
  2. package/dist/components/ds-badge.js +1 -1
  3. package/dist/components/ds-badge.js.map +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-action.js +1 -1
  7. package/dist/components/ds-bar-nav.js +1 -1
  8. package/dist/components/ds-bar-nav.js.map +1 -1
  9. package/dist/components/ds-button-group.js +1 -1
  10. package/dist/components/ds-checkbox.js +1 -1
  11. package/dist/components/{ds-label-wrap.d.ts → ds-chip.d.ts} +4 -4
  12. package/dist/components/ds-chip.js +2 -0
  13. package/dist/components/ds-chip.js.map +1 -0
  14. package/dist/components/ds-divider.js +1 -1
  15. package/dist/components/ds-divider.js.map +1 -1
  16. package/dist/components/ds-empty-state.js +1 -1
  17. package/dist/components/ds-fade.js +1 -1
  18. package/dist/components/ds-fade.js.map +1 -1
  19. package/dist/components/ds-field.js +1 -1
  20. package/dist/components/ds-header.js +1 -1
  21. package/dist/components/ds-icon.js +1 -1
  22. package/dist/components/ds-input.js +1 -1
  23. package/dist/components/ds-modal.js +1 -1
  24. package/dist/components/ds-modal.js.map +1 -1
  25. package/dist/components/ds-panel-nav.js +1 -1
  26. package/dist/components/ds-panel-nav.js.map +1 -1
  27. package/dist/components/ds-radio-group.js +1 -1
  28. package/dist/components/ds-radio-group.js.map +1 -1
  29. package/dist/components/ds-scrollbar.js +1 -1
  30. package/dist/components/ds-select.js +1 -1
  31. package/dist/components/ds-sidebar.js +1 -1
  32. package/dist/components/ds-sidebar.js.map +1 -1
  33. package/dist/components/ds-slider.js +1 -1
  34. package/dist/components/ds-slider.js.map +1 -1
  35. package/dist/components/ds-surface.js +1 -1
  36. package/dist/components/ds-tab-group-nav.js +1 -1
  37. package/dist/components/ds-tab-group.js +1 -1
  38. package/dist/components/ds-tab-group.js.map +1 -1
  39. package/dist/components/ds-table.js +1 -1
  40. package/dist/components/ds-table.js.map +1 -1
  41. package/dist/components/ds-tag.js +1 -1
  42. package/dist/components/ds-tag.js.map +1 -1
  43. package/dist/components/ds-text.js +1 -1
  44. package/dist/components/ds-toast-provider.js +1 -1
  45. package/dist/components/ds-toggle-button-group.js +1 -1
  46. package/dist/components/ds-toggle-button.js +1 -1
  47. package/dist/components/ds-toggle.js +1 -1
  48. package/dist/components/ds-tooltip.js +1 -1
  49. package/dist/components/p-B_enucPQ.js +2 -0
  50. package/dist/components/p-B_enucPQ.js.map +1 -0
  51. package/dist/components/p-DOtcc9UU.js +2 -0
  52. package/dist/components/p-DOtcc9UU.js.map +1 -0
  53. package/dist/components/{p-H89PzqGU.js → p-DrDrPeVV.js} +2 -2
  54. package/dist/components/p-OT-Q-NxH.js +2 -0
  55. package/dist/components/p-OT-Q-NxH.js.map +1 -0
  56. package/dist/components/{p-Dd0GrKaF.js → p-U1A-rprN.js} +2 -2
  57. package/dist/components/p-X1N_ggsY.js +2 -0
  58. package/dist/components/p-X1N_ggsY.js.map +1 -0
  59. package/dist/components/p-qfvbc9w-.js +2 -0
  60. package/dist/components/p-qfvbc9w-.js.map +1 -0
  61. package/dist/types/.stencil/Badge/Badge.d.ts +12 -0
  62. package/dist/types/.stencil/Badge/index.d.ts +1 -1
  63. package/dist/types/.stencil/Chip/Chip.d.ts +30 -0
  64. package/dist/types/.stencil/Chip/index.d.ts +2 -0
  65. package/dist/types/.stencil/Divider/Divider.d.ts +14 -0
  66. package/dist/types/.stencil/Fade/Fade.d.ts +17 -2
  67. package/dist/types/.stencil/Fade/index.d.ts +1 -1
  68. package/dist/types/.stencil/Tag/Tag.d.ts +0 -17
  69. package/dist/types/.stencil/Tag/index.d.ts +1 -1
  70. package/dist/types/.stencil/Text/Text.d.ts +2 -0
  71. package/dist/types/components.d.ts +283 -143
  72. package/package.json +12 -5
  73. package/src/angular/index.ts +1 -1
  74. package/src/angular/proxies.ts +51 -51
  75. package/src/react/components.ts +1 -1
  76. package/src/react/ds-chip.ts +35 -0
  77. package/src/react/ds-tag.ts +3 -12
  78. package/src/wc/components/Badge/Badge.tsx +58 -8
  79. package/src/wc/components/Badge/index.ts +1 -1
  80. package/src/wc/components/Banner/Banner.tsx +2 -2
  81. package/src/wc/components/BarNav/BarNav.tsx +7 -1
  82. package/src/wc/components/BarNavAction/BarNavAction.tsx +9 -1
  83. package/src/wc/components/Chip/Chip.tsx +127 -0
  84. package/src/wc/components/Chip/index.ts +1 -0
  85. package/src/wc/components/Divider/Divider.tsx +88 -4
  86. package/src/wc/components/Fade/Fade.tsx +86 -6
  87. package/src/wc/components/Fade/index.ts +1 -1
  88. package/src/wc/components/PanelNav/PanelNav.tsx +17 -1
  89. package/src/wc/components/TabGroup/TabGroup.tsx +9 -1
  90. package/src/wc/components/TabGroupNav/TabGroupNav.tsx +9 -1
  91. package/src/wc/components/Tag/Tag.tsx +3 -70
  92. package/src/wc/components/Tag/index.ts +1 -1
  93. package/src/wc/components/Text/Text.tsx +3 -0
  94. package/src/wc/components.d.ts +283 -143
  95. package/dist/components/ds-label-wrap.js +0 -2
  96. package/dist/components/ds-label-wrap.js.map +0 -1
  97. package/dist/components/p-CMyQsk0K.js +0 -2
  98. package/dist/components/p-CMyQsk0K.js.map +0 -1
  99. package/dist/components/p-Vwlmhl4E.js +0 -2
  100. package/dist/components/p-Vwlmhl4E.js.map +0 -1
  101. package/dist/components/p-woooYulR.js +0 -2
  102. package/dist/components/p-woooYulR.js.map +0 -1
  103. package/dist/types/.stencil/LabelWrap/LabelWrap.d.ts +0 -15
  104. package/src/react/ds-label-wrap.ts +0 -26
  105. package/src/wc/components/LabelWrap/LabelWrap.tsx +0 -38
  106. /package/dist/components/{p-H89PzqGU.js.map → p-DrDrPeVV.js.map} +0 -0
  107. /package/dist/components/{p-Dd0GrKaF.js.map → p-U1A-rprN.js.map} +0 -0
@@ -1,22 +1,106 @@
1
1
  import { Component, Prop, h, Host } from '@stencil/core';
2
2
 
3
3
  export type DividerOrientation = 'horizontal' | 'vertical';
4
+ export type DividerSurface =
5
+ | 'default'
6
+ | 'on-bold-background'
7
+ | 'on-strong-background'
8
+ | 'on-medium-background'
9
+ | 'on-translucent-background'
10
+ | 'navigation'
11
+ | 'media'
12
+ | 'always-dark'
13
+ | 'inverted';
14
+ export type DividerInsetToken =
15
+ | 'space-000'
16
+ | 'space-012'
17
+ | 'space-025'
18
+ | 'space-050'
19
+ | 'space-075'
20
+ | 'space-100'
21
+ | 'space-125'
22
+ | 'space-150'
23
+ | 'space-175'
24
+ | 'space-200'
25
+ | 'space-250'
26
+ | 'space-300'
27
+ | 'space-400'
28
+ | 'space-600'
29
+ | 'space-800';
30
+ export type DividerInset = 'none' | DividerInsetToken | (string & {});
31
+ export type DividerLength = 'auto' | 'full' | (string & {});
32
+
33
+ const INSET_VALUE: Record<'none' | DividerInsetToken, string> = {
34
+ none: '0',
35
+ 'space-000': 'var(--dimension-space-000)',
36
+ 'space-012': 'var(--dimension-space-012)',
37
+ 'space-025': 'var(--dimension-space-025)',
38
+ 'space-050': 'var(--dimension-space-050)',
39
+ 'space-075': 'var(--dimension-space-075)',
40
+ 'space-100': 'var(--dimension-space-100)',
41
+ 'space-125': 'var(--dimension-space-125)',
42
+ 'space-150': 'var(--dimension-space-150)',
43
+ 'space-175': 'var(--dimension-space-175)',
44
+ 'space-200': 'var(--dimension-space-200)',
45
+ 'space-250': 'var(--dimension-space-250)',
46
+ 'space-300': 'var(--dimension-space-300)',
47
+ 'space-400': 'var(--dimension-space-400)',
48
+ 'space-600': 'var(--dimension-space-600)',
49
+ 'space-800': 'var(--dimension-space-800)',
50
+ };
4
51
 
5
52
  @Component({
6
53
  tag: 'ds-divider',
7
54
  styleUrl: 'Divider.css',
8
- shadow: true,
55
+ scoped: true,
9
56
  })
10
57
  export class Divider {
11
58
  /** Direction of the divider line. Defaults to 'horizontal'. */
12
59
  @Prop() orientation: DividerOrientation = 'horizontal';
13
60
 
61
+ /** Surface context so the divider uses the right TokoMo divider token. */
62
+ @Prop() surface: DividerSurface = 'default';
63
+
64
+ /** Insets the divider from its start/end edges. Accepts spacing token names or any CSS length. */
65
+ @Prop() inset: DividerInset = 'none';
66
+
67
+ /** Visual line length. Defaults to filling the available axis. Accepts any CSS length. */
68
+ @Prop() length: DividerLength = 'auto';
69
+
70
+ /** Expose the divider as a semantic separator. Visual-only dividers are hidden from assistive tech by default. */
71
+ @Prop() semantic: boolean = false;
72
+
73
+ private resolveInset(): string {
74
+ return Object.prototype.hasOwnProperty.call(INSET_VALUE, this.inset)
75
+ ? INSET_VALUE[this.inset as keyof typeof INSET_VALUE]
76
+ : this.inset;
77
+ }
78
+
79
+ private resolveLength(): string {
80
+ return this.length === 'auto' || this.length === 'full'
81
+ ? '100%'
82
+ : this.length;
83
+ }
84
+
14
85
  render() {
86
+ const isVertical = this.orientation === 'vertical';
87
+ const hasCustomLength = this.length !== 'auto' && this.length !== 'full';
88
+
15
89
  return (
16
90
  <Host
17
- role="separator"
18
- aria-orientation={this.orientation}
19
- class={{ vertical: this.orientation === 'vertical' }}
91
+ role={this.semantic ? 'separator' : undefined}
92
+ aria-orientation={this.semantic ? this.orientation : undefined}
93
+ aria-hidden={this.semantic ? undefined : 'true'}
94
+ class={{
95
+ divider: true,
96
+ 'divider--vertical': isVertical,
97
+ 'divider--custom-length': hasCustomLength,
98
+ [`divider--surface-${this.surface}`]: true,
99
+ }}
100
+ style={{
101
+ '--_divider-inset': this.resolveInset(),
102
+ '--_divider-length': this.resolveLength(),
103
+ }}
20
104
  />
21
105
  );
22
106
  }
@@ -1,30 +1,110 @@
1
1
  import { Component, Prop, h, Host } from '@stencil/core';
2
2
 
3
3
  export type FadeSide = 'top' | 'bottom' | 'left' | 'right';
4
+ export type FadeSurface =
5
+ | 'default'
6
+ | 'primary'
7
+ | 'secondary'
8
+ | 'navigation'
9
+ | 'media'
10
+ | 'always-dark'
11
+ | 'inverted';
12
+ export type FadeSizeToken =
13
+ | 'size-000'
14
+ | 'size-050'
15
+ | 'size-075'
16
+ | 'size-100'
17
+ | 'size-150'
18
+ | 'size-200'
19
+ | 'size-250'
20
+ | 'size-300'
21
+ | 'size-400'
22
+ | 'size-500'
23
+ | 'size-600'
24
+ | 'size-800';
25
+ export type FadeSize = FadeSizeToken | (string & {});
26
+
27
+ const SIZE_VALUE: Record<FadeSizeToken, string> = {
28
+ 'size-000': 'var(--dimension-size-000)',
29
+ 'size-050': 'var(--dimension-size-050)',
30
+ 'size-075': 'var(--dimension-size-075)',
31
+ 'size-100': 'var(--dimension-size-100)',
32
+ 'size-150': 'var(--dimension-size-150)',
33
+ 'size-200': 'var(--dimension-size-200)',
34
+ 'size-250': 'var(--dimension-size-250)',
35
+ 'size-300': 'var(--dimension-size-300)',
36
+ 'size-400': 'var(--dimension-size-400)',
37
+ 'size-500': 'var(--dimension-size-500)',
38
+ 'size-600': 'var(--dimension-size-600)',
39
+ 'size-800': 'var(--dimension-size-800)',
40
+ };
41
+
42
+ const SURFACE_BACKGROUND: Record<FadeSurface, string> = {
43
+ default: 'var(--color-background-secondary)',
44
+ primary: 'var(--color-background-primary)',
45
+ secondary: 'var(--color-background-secondary)',
46
+ navigation: 'var(--color-navigation-background)',
47
+ media: 'var(--color-media-background)',
48
+ 'always-dark': 'var(--color-always-dark-background)',
49
+ inverted: 'var(--color-inverted-background)',
50
+ };
4
51
 
5
52
  @Component({
6
53
  tag: 'ds-fade',
7
54
  styleUrl: 'Fade.css',
8
- shadow: true,
55
+ scoped: true,
9
56
  })
10
57
  export class Fade {
11
- @Prop() side!: FadeSide;
12
- /** Height (or width for left/right) of the fade overlay — any CSS value. */
58
+ /** Edge where the fade is anchored. */
59
+ @Prop() side: FadeSide = 'bottom';
60
+
61
+ /** Fade depth along the fade axis. Accepts dimension size token names or any CSS length. */
62
+ @Prop() size: FadeSize = 'size-600';
63
+
64
+ /**
65
+ * Deprecated alias for size. Kept for existing consumers that pass a raw CSS length.
66
+ * Prefer `size`.
67
+ */
13
68
  @Prop() height: string | undefined;
14
- /** Background color to fade from. Defaults to var(--color-background-secondary). */
69
+
70
+ /** Surface context used to choose the fade target background token. */
71
+ @Prop() surface: FadeSurface = 'default';
72
+
73
+ /** Direct background override for contexts that already expose a resolved surface var. */
15
74
  @Prop() background: string = 'var(--color-background-secondary)';
16
75
 
76
+ /** Controls visibility without removing the element from layout/positioning. */
77
+ @Prop() visible: boolean = true;
78
+
79
+ private resolveSize(): string {
80
+ if (this.height) return this.height;
81
+
82
+ return Object.prototype.hasOwnProperty.call(SIZE_VALUE, this.size)
83
+ ? SIZE_VALUE[this.size as FadeSizeToken]
84
+ : this.size;
85
+ }
86
+
87
+ private resolveBackground(): string {
88
+ if (this.background !== 'var(--color-background-secondary)') return this.background;
89
+ return SURFACE_BACKGROUND[this.surface];
90
+ }
91
+
17
92
  render() {
93
+ const isVerticalEdge = this.side === 'top' || this.side === 'bottom';
94
+
18
95
  return (
19
96
  <Host
20
97
  aria-hidden="true"
21
98
  class={{
22
99
  fade: true,
100
+ 'fade--hidden': !this.visible,
101
+ 'fade--vertical-edge': isVerticalEdge,
102
+ 'fade--horizontal-edge': !isVerticalEdge,
23
103
  [`fade--${this.side}`]: true,
24
104
  }}
25
105
  style={{
26
- height: this.height,
27
- '--ds-fade-bg': this.background,
106
+ '--ds-fade-size': this.resolveSize(),
107
+ '--ds-fade-bg': this.resolveBackground(),
28
108
  } as Record<string, string>}
29
109
  />
30
110
  );
@@ -1 +1 @@
1
- export type { FadeSide } from './Fade';
1
+ export type { FadeSide, FadeSize, FadeSizeToken, FadeSurface } from './Fade';
@@ -517,7 +517,15 @@ export class PanelNav {
517
517
  </ds-text>
518
518
  </span>
519
519
  </span>,
520
- item.dot && <span class="panel-nav__item-dot" aria-hidden="true" />,
520
+ item.dot && (
521
+ <ds-badge
522
+ class="panel-nav__item-dot"
523
+ variant="dot"
524
+ background="var(--_nav-bg)"
525
+ label=""
526
+ aria-hidden="true"
527
+ />
528
+ ),
521
529
  ];
522
530
 
523
531
  const sharedProps = {
@@ -604,6 +612,14 @@ export class PanelNav {
604
612
 
605
613
  {/* ── Footer ── */}
606
614
  <div class="panel-nav__footer">
615
+ <ds-fade
616
+ class="panel-nav__footer-fade"
617
+ side="bottom"
618
+ size="size-600"
619
+ background="var(--_nav-bg)"
620
+ visible={!this.atBottom}
621
+ />
622
+
607
623
  {/* Left icon button — Gear in dashboard, Dashboard in settings */}
608
624
  <button
609
625
  type="button"
@@ -176,7 +176,15 @@ export class TabGroup {
176
176
  [isSelected ? 'text-body-small-emphasis' : 'text-body-small']: true,
177
177
  }}>
178
178
  {tab.label}
179
- {tab.dot && <span class="tab__dot" aria-hidden="true" />}
179
+ {tab.dot && (
180
+ <ds-badge
181
+ class="tab__dot"
182
+ variant="dot"
183
+ background="var(--_dot-ring)"
184
+ label=""
185
+ aria-hidden="true"
186
+ />
187
+ )}
180
188
  </span>
181
189
  </button>
182
190
  );
@@ -176,7 +176,15 @@ export class TabGroupNav {
176
176
  [isSelected ? 'text-body-medium-emphasis' : 'text-body-medium']: true,
177
177
  }}>
178
178
  {tab.label}
179
- {tab.dot && <span class="tab__dot" aria-hidden="true" />}
179
+ {tab.dot && (
180
+ <ds-badge
181
+ class="tab__dot"
182
+ variant="dot"
183
+ background="var(--_dot-ring)"
184
+ label=""
185
+ aria-hidden="true"
186
+ />
187
+ )}
180
188
  </span>
181
189
  </button>
182
190
  );
@@ -1,10 +1,9 @@
1
- import { Component, Prop, Event, EventEmitter, h, Host } from '@stencil/core';
1
+ import { Component, Prop, h, Host } from '@stencil/core';
2
2
 
3
3
  export type TagIntent = 'neutral' | 'brand' | 'ai' | 'negative' | 'warning' | 'caution' | 'positive';
4
4
  export type TagContrast = 'strong' | 'bold' | 'medium' | 'faint';
5
5
  export type TagElevation = 'none' | 'flat' | 'elevated';
6
6
  export type TagSize = 'md' | 'sm' | 'xs';
7
- export type TagBackground = 'faint' | 'medium' | 'bold' | 'strong' | 'always-dark';
8
7
 
9
8
  const TEXT_VARIANT: Record<TagSize, string> = {
10
9
  md: 'text-body-medium',
@@ -26,45 +25,10 @@ export class Tag {
26
25
  @Prop() elevation: TagElevation = 'none';
27
26
  @Prop() size: TagSize = 'md';
28
27
  @Prop() rounded: boolean = false;
29
- @Prop() removable: boolean = false;
30
28
  @Prop() maxWidth: string | number | undefined;
31
- @Prop() inactive: boolean = false;
32
- @Prop() background: TagBackground | undefined;
33
- @Prop({ mutable: true }) pressed: boolean = false;
34
- /** Whether the tag has a click/toggle behavior. */
35
- @Prop() interactive: boolean = false;
36
-
37
- /** Fired when the remove button is clicked. */
38
- @Event() dsRemove!: EventEmitter<void>;
39
- /** Fired when an interactive tag is clicked. */
40
- @Event() dsClick!: EventEmitter<void>;
41
- /** Fired when the pressed state toggles. */
42
- @Event() dsPressedChange!: EventEmitter<boolean>;
43
-
44
- private handleClick = () => {
45
- if (this.inactive) return;
46
- this.pressed = !this.pressed;
47
- this.dsPressedChange.emit(this.pressed);
48
- this.dsClick.emit();
49
- };
50
-
51
- private handleKeyDown = (e: KeyboardEvent) => {
52
- if (this.inactive) return;
53
- if (e.key === 'Enter' || e.key === ' ') {
54
- e.preventDefault();
55
- this.handleClick();
56
- }
57
- };
58
-
59
- private handleRemove = (e: MouseEvent) => {
60
- e.stopPropagation();
61
- this.dsRemove.emit();
62
- };
63
29
 
64
30
  render() {
65
- const cap = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
66
31
  const sz = this.size.toUpperCase() as 'MD' | 'SM' | 'XS';
67
- const hasIconSlot = true; // always render slot, consumer decides whether to fill it
68
32
  const textVariant = TEXT_VARIANT[this.size];
69
33
  const iconSize = ICON_SIZE[this.size];
70
34
 
@@ -80,29 +44,15 @@ export class Tag {
80
44
  [`tag--size-${this.size}`]: true,
81
45
  'tag--rounded': this.rounded,
82
46
  'tag--rounded-no-icon-left': this.rounded, // consumer overrides via slot presence hack
83
- 'tag--rounded-no-remove-right': this.rounded && !this.removable,
47
+ 'tag--rounded-no-remove-right': this.rounded,
84
48
  [`tag--icon-left-${sz}`]: false, // toggled by slotchange
85
- [`tag--icon-right-${sz}`]: this.removable,
86
- 'tag--removable': this.removable,
87
- 'tag--interactive': this.interactive,
88
- 'tag--inactive': this.inactive,
89
- 'tag--pressed': this.pressed,
90
- 'tag--on-medium': this.background === 'medium',
91
- 'tag--on-bold': this.background === 'bold',
92
- 'tag--on-strong': this.background === 'strong',
93
- 'tag--on-always-dark': this.background === 'always-dark',
49
+ [`tag--icon-right-${sz}`]: false,
94
50
  };
95
51
 
96
52
  return (
97
53
  <Host
98
54
  class={hostCls}
99
55
  style={maxWidthStyle}
100
- onClick={this.interactive ? this.handleClick : undefined}
101
- onKeyDown={this.interactive ? this.handleKeyDown : undefined}
102
- role={this.interactive ? 'button' : undefined}
103
- tabIndex={this.interactive ? (this.inactive ? -1 : 0) : undefined}
104
- aria-pressed={this.interactive && this.pressed !== undefined ? String(this.pressed) : undefined}
105
- aria-disabled={this.inactive || undefined}
106
56
  >
107
57
  {/* Leading icon slot — consumer provides an icon element */}
108
58
  <span class="tag__icon-slot" style={{ fontSize: `${iconSize}px`, lineHeight: '0' }}>
@@ -112,23 +62,6 @@ export class Tag {
112
62
  <span class={{ 'tag__label': true, [textVariant]: true }}>
113
63
  {this.label}
114
64
  </span>
115
- {/* Remove button */}
116
- {this.removable && (
117
- <button
118
- type="button"
119
- class="tag__remove"
120
- onClick={this.handleRemove}
121
- aria-label={`Remove ${this.label}`}
122
- tabIndex={-1}
123
- >
124
- {/* Remove icon slot — defaults to × glyph */}
125
- <slot name="remove-icon">
126
- <span class="tag__remove-x" style={{ fontSize: `${iconSize}px`, lineHeight: '0' }}>
127
-
128
- </span>
129
- </slot>
130
- </button>
131
- )}
132
65
  </Host>
133
66
  );
134
67
  }
@@ -1 +1 @@
1
- export type { TagIntent, TagContrast, TagElevation, TagSize, TagBackground } from './Tag';
1
+ export type { TagIntent, TagContrast, TagElevation, TagSize } from './Tag';
@@ -26,6 +26,7 @@ export type TextAlign = 'left' | 'center' | 'right';
26
26
  export type LineTruncation = 1 | 2 | 3 | 4 | 5 | 'none';
27
27
  export type TextWrap = 'wrap' | 'nowrap' | 'balance' | 'pretty';
28
28
  export type TextElement = 'p' | 'span' | 'div' | 'label' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
29
+ export type TextFontFeature = 'normal' | 'tabular-nums';
29
30
 
30
31
  @Component({
31
32
  tag: 'ds-text',
@@ -40,6 +41,7 @@ export class Text {
40
41
  @Prop() align: TextAlign | undefined;
41
42
  @Prop() lineTruncation: LineTruncation = 'none';
42
43
  @Prop() wrap: TextWrap | undefined;
44
+ @Prop() fontFeature: TextFontFeature = 'normal';
43
45
  @Prop() as: TextElement = 'p';
44
46
  /** Maps to `for` attribute on <label> elements. */
45
47
  @Prop() for: string | undefined;
@@ -80,6 +82,7 @@ export class Text {
80
82
  'text--align-left': this.align === 'left',
81
83
  'text--align-center': this.align === 'center',
82
84
  'text--align-right': this.align === 'right',
85
+ 'text--font-feature-tabular-nums': this.fontFeature === 'tabular-nums',
83
86
  };
84
87
 
85
88
  const style = this.color && isCustomColor(this.color)