@ds-mo/ui 1.2.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 (93) hide show
  1. package/README.md +82 -2
  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-chip.d.ts +11 -0
  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-empty-state.js +1 -1
  16. package/dist/components/ds-fade.js +1 -1
  17. package/dist/components/ds-fade.js.map +1 -1
  18. package/dist/components/ds-field.js +1 -1
  19. package/dist/components/ds-header.js +1 -1
  20. package/dist/components/ds-icon.js +1 -1
  21. package/dist/components/ds-input.js +1 -1
  22. package/dist/components/ds-modal.js +1 -1
  23. package/dist/components/ds-modal.js.map +1 -1
  24. package/dist/components/ds-panel-nav.js +1 -1
  25. package/dist/components/ds-panel-nav.js.map +1 -1
  26. package/dist/components/ds-radio-group.js +1 -1
  27. package/dist/components/ds-scrollbar.js +1 -1
  28. package/dist/components/ds-select.js +1 -1
  29. package/dist/components/ds-sidebar.js +1 -1
  30. package/dist/components/ds-sidebar.js.map +1 -1
  31. package/dist/components/ds-slider.js +1 -1
  32. package/dist/components/ds-surface.js +1 -1
  33. package/dist/components/ds-tab-group-nav.js +1 -1
  34. package/dist/components/ds-tab-group.js +1 -1
  35. package/dist/components/ds-tab-group.js.map +1 -1
  36. package/dist/components/ds-table.js +1 -1
  37. package/dist/components/ds-table.js.map +1 -1
  38. package/dist/components/ds-tag.js +1 -1
  39. package/dist/components/ds-tag.js.map +1 -1
  40. package/dist/components/ds-text.js +1 -1
  41. package/dist/components/ds-toast-provider.js +1 -1
  42. package/dist/components/ds-toggle-button-group.js +1 -1
  43. package/dist/components/ds-toggle-button.js +1 -1
  44. package/dist/components/ds-toggle.js +1 -1
  45. package/dist/components/ds-tooltip.js +1 -1
  46. package/dist/components/p-B_enucPQ.js +2 -0
  47. package/dist/components/p-B_enucPQ.js.map +1 -0
  48. package/dist/components/p-DOtcc9UU.js +2 -0
  49. package/dist/components/p-DOtcc9UU.js.map +1 -0
  50. package/dist/components/{p-H89PzqGU.js → p-DrDrPeVV.js} +2 -2
  51. package/dist/components/{p-Cj00JkJb.js → p-OT-Q-NxH.js} +2 -2
  52. package/dist/components/{p-CE5zeV1w.js → p-U1A-rprN.js} +2 -2
  53. package/dist/components/p-X1N_ggsY.js +2 -0
  54. package/dist/components/p-X1N_ggsY.js.map +1 -0
  55. package/dist/components/p-qfvbc9w-.js +2 -0
  56. package/dist/components/p-qfvbc9w-.js.map +1 -0
  57. package/dist/types/.stencil/Badge/Badge.d.ts +12 -0
  58. package/dist/types/.stencil/Badge/index.d.ts +1 -1
  59. package/dist/types/.stencil/Chip/Chip.d.ts +30 -0
  60. package/dist/types/.stencil/Chip/index.d.ts +2 -0
  61. package/dist/types/.stencil/Fade/Fade.d.ts +17 -2
  62. package/dist/types/.stencil/Fade/index.d.ts +1 -1
  63. package/dist/types/.stencil/Tag/Tag.d.ts +0 -17
  64. package/dist/types/.stencil/Tag/index.d.ts +1 -1
  65. package/dist/types/components.d.ts +226 -80
  66. package/package.json +12 -5
  67. package/src/angular/index.ts +1 -0
  68. package/src/angular/proxies.ts +47 -24
  69. package/src/react/components.ts +1 -0
  70. package/src/react/ds-chip.ts +35 -0
  71. package/src/react/ds-tag.ts +3 -12
  72. package/src/wc/components/Badge/Badge.tsx +58 -8
  73. package/src/wc/components/Badge/index.ts +1 -1
  74. package/src/wc/components/Banner/Banner.tsx +2 -2
  75. package/src/wc/components/BarNav/BarNav.tsx +7 -1
  76. package/src/wc/components/BarNavAction/BarNavAction.tsx +9 -1
  77. package/src/wc/components/Chip/Chip.tsx +127 -0
  78. package/src/wc/components/Chip/index.ts +1 -0
  79. package/src/wc/components/Fade/Fade.tsx +86 -6
  80. package/src/wc/components/Fade/index.ts +1 -1
  81. package/src/wc/components/PanelNav/PanelNav.tsx +17 -1
  82. package/src/wc/components/TabGroup/TabGroup.tsx +9 -1
  83. package/src/wc/components/TabGroupNav/TabGroupNav.tsx +9 -1
  84. package/src/wc/components/Tag/Tag.tsx +3 -70
  85. package/src/wc/components/Tag/index.ts +1 -1
  86. package/src/wc/components.d.ts +226 -80
  87. package/dist/components/p-CQQe6VNo.js +0 -2
  88. package/dist/components/p-CQQe6VNo.js.map +0 -1
  89. package/dist/components/p-Vwlmhl4E.js +0 -2
  90. package/dist/components/p-Vwlmhl4E.js.map +0 -1
  91. /package/dist/components/{p-H89PzqGU.js.map → p-DrDrPeVV.js.map} +0 -0
  92. /package/dist/components/{p-Cj00JkJb.js.map → p-OT-Q-NxH.js.map} +0 -0
  93. /package/dist/components/{p-CE5zeV1w.js.map → p-U1A-rprN.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ds-mo/ui",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "CompoMo — composable web components styled with TokoMo design tokens",
5
5
  "keywords": [
6
6
  "web-components",
@@ -62,14 +62,20 @@
62
62
  "test:e2e": "npm run build && playwright test",
63
63
  "test:e2e:install": "playwright install chromium",
64
64
  "build:docs": "stencil build --docs",
65
- "dev": "node scripts/generate-icon-catalog.mjs && stencil build --dev --watch",
66
- "storybook": "stencil build && storybook dev -p 6006",
65
+ "dev": "node scripts/generate-icon-catalog.mjs && stencil build --watch",
66
+ "storybook:ui": "storybook dev -p 6006 --no-open",
67
+ "storybook": "node scripts/storybook-dev.mjs",
67
68
  "storybook:build": "storybook build",
68
69
  "clean": "rm -rf dist",
69
70
  "typecheck": "tsc --noEmit",
70
71
  "lint": "eslint src/",
71
72
  "registry:build": "node scripts/build-registry.mjs",
72
- "mcp": "node scripts/mcp-server.mjs"
73
+ "mcp": "node scripts/mcp-server.mjs",
74
+ "figma:connect:help": "figma connect --help",
75
+ "figma:connect:publish:dry-run": "figma connect publish --dry-run -c figma.config.json --exit-on-unreadable-files",
76
+ "figma:connect:publish": "figma connect publish -c figma.config.json --exit-on-unreadable-files",
77
+ "figma:connect:preview": "figma connect preview -c figma.config.json",
78
+ "typecheck:figma": "tsc -p tsconfig.figma.json --noEmit"
73
79
  },
74
80
  "peerDependencies": {
75
81
  "@ds-mo/icons": "^6.0.0",
@@ -87,8 +93,9 @@
87
93
  },
88
94
  "devDependencies": {
89
95
  "@ds-mo/icons": "^6.0.0",
90
- "@ds-mo/tokens": "^2.9.1",
96
+ "@ds-mo/tokens": "^2.10.0",
91
97
  "@eslint/js": "^10.0.1",
98
+ "@figma/code-connect": "^1.4.8",
92
99
  "@modelcontextprotocol/sdk": "^1.29.0",
93
100
  "@playwright/test": "^1.55.0",
94
101
  "@stencil/angular-output-target": "^1.3.1",
@@ -12,6 +12,7 @@ export const DIRECTIVES = [
12
12
  d.DsButtonGroup,
13
13
  d.DsCard,
14
14
  d.DsCheckbox,
15
+ d.DsChip,
15
16
  d.DsDivider,
16
17
  d.DsEmptyState,
17
18
  d.DsFade,
@@ -36,14 +36,14 @@ export declare interface DsAccordion extends Components.DsAccordion {
36
36
 
37
37
 
38
38
  @ProxyCmp({
39
- inputs: ['count', 'isSelected', 'label']
39
+ inputs: ['background', 'count', 'isSelected', 'label', 'max', 'surface', 'variant']
40
40
  })
41
41
  @Component({
42
42
  selector: 'ds-badge',
43
43
  changeDetection: ChangeDetectionStrategy.OnPush,
44
44
  template: '<ng-content></ng-content>',
45
45
  // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
46
- inputs: ['count', 'isSelected', 'label'],
46
+ inputs: ['background', 'count', 'isSelected', 'label', 'max', 'surface', 'variant'],
47
47
  standalone: false
48
48
  })
49
49
  export class DsBadge {
@@ -296,6 +296,46 @@ export declare interface DsCheckbox extends Components.DsCheckbox {
296
296
  }
297
297
 
298
298
 
299
+ @ProxyCmp({
300
+ inputs: ['background', 'contrast', 'elevation', 'inactive', 'intent', 'label', 'maxWidth', 'pressed', 'removable', 'rounded', 'size']
301
+ })
302
+ @Component({
303
+ selector: 'ds-chip',
304
+ changeDetection: ChangeDetectionStrategy.OnPush,
305
+ template: '<ng-content></ng-content>',
306
+ // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
307
+ inputs: ['background', 'contrast', 'elevation', 'inactive', 'intent', { name: 'label', required: true }, 'maxWidth', 'pressed', 'removable', 'rounded', 'size'],
308
+ outputs: ['dsRemove', 'dsClick', 'dsPressedChange'],
309
+ standalone: false
310
+ })
311
+ export class DsChip {
312
+ protected el: HTMLDsChipElement;
313
+ @Output() dsRemove = new EventEmitter<CustomEvent<void>>();
314
+ @Output() dsClick = new EventEmitter<CustomEvent<void>>();
315
+ @Output() dsPressedChange = new EventEmitter<CustomEvent<boolean>>();
316
+ constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
317
+ c.detach();
318
+ this.el = r.nativeElement;
319
+ }
320
+ }
321
+
322
+
323
+ export declare interface DsChip extends Components.DsChip {
324
+ /**
325
+ * Fired when the remove button is clicked.
326
+ */
327
+ dsRemove: EventEmitter<CustomEvent<void>>;
328
+ /**
329
+ * Fired when an interactive chip is clicked.
330
+ */
331
+ dsClick: EventEmitter<CustomEvent<void>>;
332
+ /**
333
+ * Fired when the pressed state toggles.
334
+ */
335
+ dsPressedChange: EventEmitter<CustomEvent<boolean>>;
336
+ }
337
+
338
+
299
339
  @ProxyCmp({
300
340
  inputs: ['inset', 'length', 'orientation', 'semantic', 'surface']
301
341
  })
@@ -343,14 +383,14 @@ export declare interface DsEmptyState extends Components.DsEmptyState {}
343
383
 
344
384
 
345
385
  @ProxyCmp({
346
- inputs: ['background', 'height', 'side']
386
+ inputs: ['background', 'height', 'side', 'size', 'surface', 'visible']
347
387
  })
348
388
  @Component({
349
389
  selector: 'ds-fade',
350
390
  changeDetection: ChangeDetectionStrategy.OnPush,
351
391
  template: '<ng-content></ng-content>',
352
392
  // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
353
- inputs: ['background', 'height', { name: 'side', required: true }],
393
+ inputs: ['background', 'height', 'side', 'size', 'surface', 'visible'],
354
394
  standalone: false
355
395
  })
356
396
  export class DsFade {
@@ -902,22 +942,18 @@ export declare interface DsTable extends Components.DsTable {
902
942
 
903
943
 
904
944
  @ProxyCmp({
905
- inputs: ['background', 'contrast', 'elevation', 'inactive', 'intent', 'interactive', 'label', 'maxWidth', 'pressed', 'removable', 'rounded', 'size']
945
+ inputs: ['contrast', 'elevation', 'intent', 'label', 'maxWidth', 'rounded', 'size']
906
946
  })
907
947
  @Component({
908
948
  selector: 'ds-tag',
909
949
  changeDetection: ChangeDetectionStrategy.OnPush,
910
950
  template: '<ng-content></ng-content>',
911
951
  // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
912
- inputs: ['background', 'contrast', 'elevation', 'inactive', 'intent', 'interactive', { name: 'label', required: true }, 'maxWidth', 'pressed', 'removable', 'rounded', 'size'],
913
- outputs: ['dsRemove', 'dsClick', 'dsPressedChange'],
952
+ inputs: ['contrast', 'elevation', 'intent', { name: 'label', required: true }, 'maxWidth', 'rounded', 'size'],
914
953
  standalone: false
915
954
  })
916
955
  export class DsTag {
917
956
  protected el: HTMLDsTagElement;
918
- @Output() dsRemove = new EventEmitter<CustomEvent<void>>();
919
- @Output() dsClick = new EventEmitter<CustomEvent<void>>();
920
- @Output() dsPressedChange = new EventEmitter<CustomEvent<boolean>>();
921
957
  constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
922
958
  c.detach();
923
959
  this.el = r.nativeElement;
@@ -925,20 +961,7 @@ export class DsTag {
925
961
  }
926
962
 
927
963
 
928
- export declare interface DsTag extends Components.DsTag {
929
- /**
930
- * Fired when the remove button is clicked.
931
- */
932
- dsRemove: EventEmitter<CustomEvent<void>>;
933
- /**
934
- * Fired when an interactive tag is clicked.
935
- */
936
- dsClick: EventEmitter<CustomEvent<void>>;
937
- /**
938
- * Fired when the pressed state toggles.
939
- */
940
- dsPressedChange: EventEmitter<CustomEvent<boolean>>;
941
- }
964
+ export declare interface DsTag extends Components.DsTag {}
942
965
 
943
966
 
944
967
  @ProxyCmp({
@@ -14,6 +14,7 @@ export { DsButtonGroup } from "./ds-button-group.js";
14
14
  export { DsButton } from "./ds-button.js";
15
15
  export { DsCard } from "./ds-card.js";
16
16
  export { DsCheckbox } from "./ds-checkbox.js";
17
+ export { DsChip } from "./ds-chip.js";
17
18
  export { DsDivider } from "./ds-divider.js";
18
19
  export { DsEmptyState } from "./ds-empty-state.js";
19
20
  export { DsFade } from "./ds-fade.js";
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * This file was automatically generated by the Stencil React Output Target.
5
+ * Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
6
+ */
7
+
8
+ /* eslint-disable */
9
+
10
+ import type { EventName, StencilReactComponent } from '@stencil/react-output-target/runtime';
11
+ import { createComponent } from '@stencil/react-output-target/runtime';
12
+ import React from 'react';
13
+
14
+ import { type DsChipCustomEvent } from "@ds-mo/ui";
15
+ import type { Components } from "@ds-mo/ui/dist/components";
16
+ import { DsChip as DsChipElement, defineCustomElement as defineDsChip } from "@ds-mo/ui/dist/components/ds-chip.js";
17
+
18
+ export type DsChipEvents = {
19
+ onDsRemove: EventName<DsChipCustomEvent<void>>,
20
+ onDsClick: EventName<DsChipCustomEvent<void>>,
21
+ onDsPressedChange: EventName<DsChipCustomEvent<boolean>>
22
+ };
23
+
24
+ export const DsChip: StencilReactComponent<DsChipElement, DsChipEvents, Components.DsChip> = /*@__PURE__*/ createComponent<DsChipElement, DsChipEvents, Components.DsChip>({
25
+ tagName: 'ds-chip',
26
+ elementClass: DsChipElement,
27
+ // @ts-ignore - ignore potential React type mismatches between the Stencil Output Target and your project.
28
+ react: React,
29
+ events: {
30
+ onDsRemove: 'dsRemove',
31
+ onDsClick: 'dsClick',
32
+ onDsPressedChange: 'dsPressedChange'
33
+ } as DsChipEvents,
34
+ defineCustomElement: defineDsChip
35
+ });
@@ -7,29 +7,20 @@
7
7
 
8
8
  /* eslint-disable */
9
9
 
10
- import type { EventName, StencilReactComponent } from '@stencil/react-output-target/runtime';
10
+ import type { StencilReactComponent } from '@stencil/react-output-target/runtime';
11
11
  import { createComponent } from '@stencil/react-output-target/runtime';
12
12
  import React from 'react';
13
13
 
14
- import { type DsTagCustomEvent } from "@ds-mo/ui";
15
14
  import type { Components } from "@ds-mo/ui/dist/components";
16
15
  import { DsTag as DsTagElement, defineCustomElement as defineDsTag } from "@ds-mo/ui/dist/components/ds-tag.js";
17
16
 
18
- export type DsTagEvents = {
19
- onDsRemove: EventName<DsTagCustomEvent<void>>,
20
- onDsClick: EventName<DsTagCustomEvent<void>>,
21
- onDsPressedChange: EventName<DsTagCustomEvent<boolean>>
22
- };
17
+ export type DsTagEvents = NonNullable<unknown>;
23
18
 
24
19
  export const DsTag: StencilReactComponent<DsTagElement, DsTagEvents, Components.DsTag> = /*@__PURE__*/ createComponent<DsTagElement, DsTagEvents, Components.DsTag>({
25
20
  tagName: 'ds-tag',
26
21
  elementClass: DsTagElement,
27
22
  // @ts-ignore - ignore potential React type mismatches between the Stencil Output Target and your project.
28
23
  react: React,
29
- events: {
30
- onDsRemove: 'dsRemove',
31
- onDsClick: 'dsClick',
32
- onDsPressedChange: 'dsPressedChange'
33
- } as DsTagEvents,
24
+ events: {} as DsTagEvents,
34
25
  defineCustomElement: defineDsTag
35
26
  });
@@ -1,32 +1,82 @@
1
1
  import { Component, Prop, h, Host } from '@stencil/core';
2
2
 
3
+ export type BadgeVariant = 'counter' | 'dot';
4
+ export type BadgeSurface =
5
+ | 'default'
6
+ | 'primary'
7
+ | 'secondary'
8
+ | 'medium'
9
+ | 'bold'
10
+ | 'strong'
11
+ | 'navigation'
12
+ | 'always-dark';
13
+
14
+ const SURFACE_RING: Record<BadgeSurface, string> = {
15
+ default: 'var(--color-background-secondary)',
16
+ primary: 'var(--color-background-primary)',
17
+ secondary: 'var(--color-background-secondary)',
18
+ medium: 'var(--color-background-medium-neutral)',
19
+ bold: 'var(--color-background-bold-neutral)',
20
+ strong: 'var(--color-background-strong-neutral)',
21
+ navigation: 'var(--color-navigation-background)',
22
+ 'always-dark': 'var(--color-always-dark-background)',
23
+ };
24
+
3
25
  @Component({
4
26
  tag: 'ds-badge',
5
27
  styleUrl: 'Badge.css',
6
28
  scoped: true,
7
29
  })
8
30
  export class Badge {
31
+ /** Render as a compact counter or notification dot. */
32
+ @Prop() variant: BadgeVariant = 'counter';
33
+
34
+ /** Count shown for counter badges. Count 0 hides the badge. */
9
35
  @Prop() count: number = 0;
36
+
37
+ /** Highest count shown before compacting to "+". */
38
+ @Prop() max: number = 9;
39
+
40
+ /** Surface context for the ring around dots. */
41
+ @Prop() surface: BadgeSurface = 'default';
42
+
43
+ /** Direct ring background override for component-local surfaces. */
44
+ @Prop() background: string | undefined;
45
+
46
+ /** Deprecated alias for selected counter styling. Prefer context-specific color in the parent. */
10
47
  @Prop() isSelected: boolean = false;
48
+
11
49
  /** Accessible label. Defaults to the count as a string. */
12
50
  @Prop() label: string | undefined;
13
51
 
14
52
  render() {
15
- if (this.count === 0) return <Host style={{ display: 'none' }} />;
53
+ const isDot = this.variant === 'dot';
54
+
55
+ if (!isDot && this.count === 0) return <Host style={{ display: 'none' }} />;
16
56
 
17
- const display = this.count > 9 ? '+' : String(this.count);
18
- const ariaLabel = this.label ?? String(this.count);
57
+ const display = this.count > this.max ? '+' : String(this.count);
58
+ const ariaLabel = this.label ?? (isDot ? 'Notification' : String(this.count));
59
+ const ring = this.background ?? SURFACE_RING[this.surface];
19
60
 
20
61
  return (
21
- <Host class="badge" aria-label={ariaLabel}>
62
+ <Host
63
+ class={{
64
+ badge: true,
65
+ 'badge--counter': !isDot,
66
+ 'badge--dot': isDot,
67
+ 'badge--selected': this.isSelected,
68
+ }}
69
+ aria-label={ariaLabel}
70
+ style={{ '--_badge-ring': ring }}
71
+ >
22
72
  <span
23
73
  class={{
24
- 'text-caption-emphasis': true,
25
- 'badge__text': true,
26
- 'badge__text--selected': this.isSelected,
74
+ 'badge__mark': true,
75
+ 'text-caption-emphasis': !isDot,
27
76
  }}
77
+ aria-hidden="true"
28
78
  >
29
- {display}
79
+ {!isDot && display}
30
80
  </span>
31
81
  </Host>
32
82
  );
@@ -1 +1 @@
1
- export type {} from './Badge';
1
+ export type { BadgeVariant, BadgeSurface } from './Badge';
@@ -87,8 +87,8 @@ export class Banner {
87
87
 
88
88
  private getElevation(): string {
89
89
  return this.floating
90
- ? 'var(--effect-elevation-elevated-floating, var(--effect-elevation-floating))'
91
- : 'var(--effect-elevation-elevated)';
90
+ ? 'var(--effect-elevation-elevated-floating)'
91
+ : 'var(--effect-elevation-elevated-sm)';
92
92
  }
93
93
 
94
94
  render() {
@@ -551,7 +551,13 @@ export class BarNav {
551
551
  {this.activeTabLabel}
552
552
  </span>
553
553
  {this.activeTabHasDot && (
554
- <span class="bar-nav__tab-trigger-dot" aria-hidden="true" />
554
+ <ds-badge
555
+ class="bar-nav__tab-trigger-dot"
556
+ variant="dot"
557
+ background="var(--_dot-ring)"
558
+ label=""
559
+ aria-hidden="true"
560
+ />
555
561
  )}
556
562
  </span>
557
563
  <ds-icon
@@ -58,7 +58,15 @@ export class BarNavAction {
58
58
  >
59
59
  <span class="action-btn__icon-wrap">
60
60
  <ds-icon name={this.icon} size="md" color="inherit" />
61
- {this.dot && <span class="action-btn__dot" aria-hidden="true" />}
61
+ {this.dot && (
62
+ <ds-badge
63
+ class="action-btn__dot"
64
+ variant="dot"
65
+ background="var(--_dot-ring)"
66
+ label=""
67
+ aria-hidden="true"
68
+ />
69
+ )}
62
70
  </span>
63
71
  </button>
64
72
  </Host>
@@ -0,0 +1,127 @@
1
+ import { Component, Prop, Event, EventEmitter, h, Host } from '@stencil/core';
2
+
3
+ export type ChipIntent = 'neutral' | 'brand' | 'ai' | 'negative' | 'warning' | 'caution' | 'positive';
4
+ export type ChipContrast = 'strong' | 'bold' | 'medium' | 'faint';
5
+ export type ChipElevation = 'none' | 'flat' | 'elevated';
6
+ export type ChipSize = 'md' | 'sm' | 'xs';
7
+ export type ChipBackground = 'faint' | 'medium' | 'bold' | 'strong' | 'always-dark';
8
+
9
+ const TEXT_VARIANT: Record<ChipSize, string> = {
10
+ md: 'text-body-medium',
11
+ sm: 'text-body-small',
12
+ xs: 'text-caption',
13
+ };
14
+
15
+ const ICON_SIZE: Record<ChipSize, number> = { md: 20, sm: 16, xs: 12 };
16
+
17
+ @Component({
18
+ tag: 'ds-chip',
19
+ styleUrl: 'Chip.css',
20
+ scoped: true,
21
+ })
22
+ export class Chip {
23
+ @Prop() label!: string;
24
+ @Prop() intent: ChipIntent = 'neutral';
25
+ @Prop() contrast: ChipContrast = 'faint';
26
+ @Prop() elevation: ChipElevation = 'none';
27
+ @Prop() size: ChipSize = 'md';
28
+ @Prop() rounded: boolean = false;
29
+ @Prop() removable: boolean = false;
30
+ @Prop() maxWidth: string | number | undefined;
31
+ @Prop() inactive: boolean = false;
32
+ @Prop() background: ChipBackground | undefined;
33
+ @Prop({ mutable: true }) pressed: boolean = false;
34
+
35
+ /** Fired when the remove button is clicked. */
36
+ @Event() dsRemove!: EventEmitter<void>;
37
+ /** Fired when an interactive chip is clicked. */
38
+ @Event() dsClick!: EventEmitter<void>;
39
+ /** Fired when the pressed state toggles. */
40
+ @Event() dsPressedChange!: EventEmitter<boolean>;
41
+
42
+ private handleClick = () => {
43
+ if (this.inactive) return;
44
+ this.pressed = !this.pressed;
45
+ this.dsPressedChange.emit(this.pressed);
46
+ this.dsClick.emit();
47
+ };
48
+
49
+ private handleKeyDown = (e: KeyboardEvent) => {
50
+ if (this.inactive) return;
51
+ if (e.key === 'Enter' || e.key === ' ') {
52
+ e.preventDefault();
53
+ this.handleClick();
54
+ }
55
+ };
56
+
57
+ private handleRemove = (e: MouseEvent) => {
58
+ e.stopPropagation();
59
+ this.dsRemove.emit();
60
+ };
61
+
62
+ render() {
63
+ const sz = this.size.toUpperCase() as 'MD' | 'SM' | 'XS';
64
+ const textVariant = TEXT_VARIANT[this.size];
65
+ const iconSize = ICON_SIZE[this.size];
66
+
67
+ const maxWidthStyle = this.maxWidth != null
68
+ ? { maxWidth: typeof this.maxWidth === 'number' ? `${this.maxWidth}px` : this.maxWidth }
69
+ : undefined;
70
+
71
+ const hostCls: Record<string, boolean> = {
72
+ 'tag': true,
73
+ [`tag--intent-${this.intent}`]: true,
74
+ [`tag--contrast-${this.contrast}`]: true,
75
+ [`tag--elevation-${this.elevation}`]: true,
76
+ [`tag--size-${this.size}`]: true,
77
+ 'tag--rounded': this.rounded,
78
+ 'tag--rounded-no-icon-left': this.rounded,
79
+ 'tag--rounded-no-remove-right': this.rounded && !this.removable,
80
+ [`tag--icon-left-${sz}`]: false,
81
+ [`tag--icon-right-${sz}`]: this.removable,
82
+ 'tag--removable': this.removable,
83
+ 'tag--interactive': true,
84
+ 'tag--inactive': this.inactive,
85
+ 'tag--pressed': this.pressed,
86
+ 'tag--on-medium': this.background === 'medium',
87
+ 'tag--on-bold': this.background === 'bold',
88
+ 'tag--on-strong': this.background === 'strong',
89
+ 'tag--on-always-dark': this.background === 'always-dark',
90
+ };
91
+
92
+ return (
93
+ <Host
94
+ class={hostCls}
95
+ style={maxWidthStyle}
96
+ onClick={this.handleClick}
97
+ onKeyDown={this.handleKeyDown}
98
+ role="button"
99
+ tabIndex={this.inactive ? -1 : 0}
100
+ aria-pressed={String(this.pressed)}
101
+ aria-disabled={this.inactive || undefined}
102
+ >
103
+ <span class="tag__icon-slot" style={{ fontSize: `${iconSize}px`, lineHeight: '0' }}>
104
+ <slot name="icon" />
105
+ </span>
106
+ <span class={{ 'tag__label': true, [textVariant]: true }}>
107
+ {this.label}
108
+ </span>
109
+ {this.removable && (
110
+ <button
111
+ type="button"
112
+ class="tag__remove"
113
+ onClick={this.handleRemove}
114
+ aria-label={`Remove ${this.label}`}
115
+ tabIndex={-1}
116
+ >
117
+ <slot name="remove-icon">
118
+ <span class="tag__remove-x" style={{ fontSize: `${iconSize}px`, lineHeight: '0' }}>
119
+
120
+ </span>
121
+ </slot>
122
+ </button>
123
+ )}
124
+ </Host>
125
+ );
126
+ }
127
+ }
@@ -0,0 +1 @@
1
+ export type { ChipIntent, ChipContrast, ChipElevation, ChipSize, ChipBackground } from './Chip';
@@ -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';