@arbor-education/design-system.components 0.21.1 → 0.22.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 (121) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/component-library.md +15 -14
  3. package/dist/components/articleCard/ArticleCard.d.ts +2 -2
  4. package/dist/components/articleCard/ArticleCard.d.ts.map +1 -1
  5. package/dist/components/articleCard/ArticleCard.js +3 -3
  6. package/dist/components/articleCard/ArticleCard.js.map +1 -1
  7. package/dist/components/articleCard/ArticleCard.stories.d.ts +11 -3
  8. package/dist/components/articleCard/ArticleCard.stories.d.ts.map +1 -1
  9. package/dist/components/articleCard/ArticleCard.stories.js +16 -11
  10. package/dist/components/articleCard/ArticleCard.stories.js.map +1 -1
  11. package/dist/components/iconText/IconText.d.ts +43 -0
  12. package/dist/components/iconText/IconText.d.ts.map +1 -0
  13. package/dist/components/iconText/IconText.js +29 -0
  14. package/dist/components/iconText/IconText.js.map +1 -0
  15. package/dist/components/{icoText/IcoText.stories.d.ts → iconText/IconText.stories.d.ts} +8 -9
  16. package/dist/components/iconText/IconText.stories.d.ts.map +1 -0
  17. package/dist/components/{icoText/IcoText.stories.js → iconText/IconText.stories.js} +81 -81
  18. package/dist/components/iconText/IconText.stories.js.map +1 -0
  19. package/dist/components/iconText/IconText.test.d.ts +2 -0
  20. package/dist/components/iconText/IconText.test.d.ts.map +1 -0
  21. package/dist/components/{icoText/IcoText.test.js → iconText/IconText.test.js} +6 -6
  22. package/dist/components/iconText/IconText.test.js.map +1 -0
  23. package/dist/components/modal/Modal.d.ts +1 -0
  24. package/dist/components/modal/Modal.d.ts.map +1 -1
  25. package/dist/components/modal/Modal.js +2 -2
  26. package/dist/components/modal/Modal.js.map +1 -1
  27. package/dist/components/tag/Tag.d.ts +14 -1
  28. package/dist/components/tag/Tag.d.ts.map +1 -1
  29. package/dist/components/tag/Tag.js +9 -3
  30. package/dist/components/tag/Tag.js.map +1 -1
  31. package/dist/components/tag/Tag.stories.d.ts +1 -1
  32. package/dist/components/tag/Tag.stories.d.ts.map +1 -1
  33. package/dist/components/tag/Tag.stories.js +3 -3
  34. package/dist/components/tag/Tag.stories.js.map +1 -1
  35. package/dist/components/tag/Tag.test.js +36 -5
  36. package/dist/components/tag/Tag.test.js.map +1 -1
  37. package/dist/components/tagList/TagList.d.ts +49 -0
  38. package/dist/components/tagList/TagList.d.ts.map +1 -0
  39. package/dist/components/tagList/TagList.js +114 -0
  40. package/dist/components/tagList/TagList.js.map +1 -0
  41. package/dist/components/tagList/TagList.stories.d.ts +130 -0
  42. package/dist/components/tagList/TagList.stories.d.ts.map +1 -0
  43. package/dist/components/tagList/TagList.stories.js +443 -0
  44. package/dist/components/tagList/TagList.stories.js.map +1 -0
  45. package/dist/components/{icoText/IcoText.test.d.ts → tagList/TagList.test.d.ts} +1 -1
  46. package/dist/components/tagList/TagList.test.d.ts.map +1 -0
  47. package/dist/components/tagList/TagList.test.js +246 -0
  48. package/dist/components/tagList/TagList.test.js.map +1 -0
  49. package/dist/components/tagList/useTagListCollapsedLayout.d.ts +19 -0
  50. package/dist/components/tagList/useTagListCollapsedLayout.d.ts.map +1 -0
  51. package/dist/components/tagList/useTagListCollapsedLayout.js +48 -0
  52. package/dist/components/tagList/useTagListCollapsedLayout.js.map +1 -0
  53. package/dist/components/tagList/useVisibleTags.d.ts +18 -0
  54. package/dist/components/tagList/useVisibleTags.d.ts.map +1 -0
  55. package/dist/components/tagList/useVisibleTags.js +41 -0
  56. package/dist/components/tagList/useVisibleTags.js.map +1 -0
  57. package/dist/index.css +130 -10
  58. package/dist/index.css.map +1 -1
  59. package/dist/index.d.ts +3 -1
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +2 -1
  62. package/dist/index.js.map +1 -1
  63. package/dist/utils/hooks/useElementWidth.d.ts +2 -0
  64. package/dist/utils/hooks/useElementWidth.d.ts.map +1 -0
  65. package/dist/utils/hooks/useElementWidth.js +30 -0
  66. package/dist/utils/hooks/useElementWidth.js.map +1 -0
  67. package/dist/utils/hooks/useMeasuredChildWidths.d.ts +8 -0
  68. package/dist/utils/hooks/useMeasuredChildWidths.d.ts.map +1 -0
  69. package/dist/utils/hooks/useMeasuredChildWidths.js +26 -0
  70. package/dist/utils/hooks/useMeasuredChildWidths.js.map +1 -0
  71. package/dist/utils/hooks/useRovingFocus.d.ts +18 -0
  72. package/dist/utils/hooks/useRovingFocus.d.ts.map +1 -0
  73. package/dist/utils/hooks/useRovingFocus.js +130 -0
  74. package/dist/utils/hooks/useRovingFocus.js.map +1 -0
  75. package/dist/utils/hooks/useRovingFocus.test.d.ts +2 -0
  76. package/dist/utils/hooks/useRovingFocus.test.d.ts.map +1 -0
  77. package/dist/utils/hooks/useRovingFocus.test.js +59 -0
  78. package/dist/utils/hooks/useRovingFocus.test.js.map +1 -0
  79. package/dist/utils/spacedWidths.d.ts +3 -0
  80. package/dist/utils/spacedWidths.d.ts.map +1 -0
  81. package/dist/utils/spacedWidths.js +28 -0
  82. package/dist/utils/spacedWidths.js.map +1 -0
  83. package/dist/utils/spacedWidths.test.d.ts +2 -0
  84. package/dist/utils/spacedWidths.test.d.ts.map +1 -0
  85. package/dist/utils/spacedWidths.test.js +17 -0
  86. package/dist/utils/spacedWidths.test.js.map +1 -0
  87. package/package.json +1 -1
  88. package/src/components/articleCard/ArticleCard.stories.tsx +17 -12
  89. package/src/components/articleCard/ArticleCard.tsx +9 -9
  90. package/src/components/{icoText/IcoText.stories.tsx → iconText/IconText.stories.tsx} +112 -112
  91. package/src/components/{icoText/IcoText.test.tsx → iconText/IconText.test.tsx} +10 -10
  92. package/src/components/{icoText/IcoText.tsx → iconText/IconText.tsx} +27 -20
  93. package/src/components/modal/Modal.tsx +5 -1
  94. package/src/components/tag/Tag.stories.tsx +4 -4
  95. package/src/components/tag/Tag.test.tsx +62 -5
  96. package/src/components/tag/Tag.tsx +61 -3
  97. package/src/components/tag/tag.scss +80 -9
  98. package/src/components/tagList/TagList.stories.tsx +564 -0
  99. package/src/components/tagList/TagList.test.tsx +342 -0
  100. package/src/components/tagList/TagList.tsx +296 -0
  101. package/src/components/tagList/tagList.scss +56 -0
  102. package/src/components/tagList/useTagListCollapsedLayout.ts +83 -0
  103. package/src/components/tagList/useVisibleTags.ts +74 -0
  104. package/src/index.scss +2 -1
  105. package/src/index.ts +3 -1
  106. package/src/tokens.scss +2 -1
  107. package/src/utils/hooks/useElementWidth.ts +39 -0
  108. package/src/utils/hooks/useMeasuredChildWidths.ts +39 -0
  109. package/src/utils/hooks/useRovingFocus.test.tsx +105 -0
  110. package/src/utils/hooks/useRovingFocus.ts +163 -0
  111. package/src/utils/spacedWidths.test.ts +20 -0
  112. package/src/utils/spacedWidths.ts +37 -0
  113. package/dist/components/icoText/IcoText.d.ts +0 -37
  114. package/dist/components/icoText/IcoText.d.ts.map +0 -1
  115. package/dist/components/icoText/IcoText.js +0 -29
  116. package/dist/components/icoText/IcoText.js.map +0 -1
  117. package/dist/components/icoText/IcoText.stories.d.ts.map +0 -1
  118. package/dist/components/icoText/IcoText.stories.js.map +0 -1
  119. package/dist/components/icoText/IcoText.test.d.ts.map +0 -1
  120. package/dist/components/icoText/IcoText.test.js.map +0 -1
  121. /package/src/components/{icoText/icoText.scss → iconText/iconText.scss} +0 -0
@@ -3,20 +3,20 @@ import type { IconName } from 'Components/icon/allowedIcons';
3
3
  import { Icon } from 'Components/icon/Icon';
4
4
  import { Children, isValidElement } from 'react';
5
5
 
6
- export type IcoTextProps = {
6
+ export type IconTextProps = {
7
7
  children?: React.ReactNode;
8
8
  className?: string;
9
9
  };
10
10
 
11
- export type IcoTextHeadingProps = React.HTMLAttributes<HTMLHeadingElement> & {
11
+ export type IconTextHeadingProps = React.HTMLAttributes<HTMLHeadingElement> & {
12
12
  children: React.ReactNode;
13
13
  };
14
14
 
15
- export type IcoTextParagraphProps = React.HTMLAttributes<HTMLParagraphElement> & {
15
+ export type IconTextParagraphProps = React.HTMLAttributes<HTMLParagraphElement> & {
16
16
  children: React.ReactNode;
17
17
  };
18
18
 
19
- export type IcoTextIconProps = {
19
+ export type IconTextIconProps = {
20
20
  className?: string;
21
21
  color?: Icon.Props['color'];
22
22
  name: IconName;
@@ -24,33 +24,33 @@ export type IcoTextIconProps = {
24
24
  size?: 12 | 16 | 24;
25
25
  };
26
26
 
27
- const IcoTextHeading = ({
27
+ const IconTextHeading = ({
28
28
  children,
29
29
  className,
30
30
  ...rest
31
- }: IcoTextHeadingProps): React.JSX.Element => (
31
+ }: IconTextHeadingProps): React.JSX.Element => (
32
32
  <h4 className={classNames('ds-ico-text__heading', className)} {...rest}>
33
33
  {children}
34
34
  </h4>
35
35
  );
36
36
 
37
- const IcoTextParagraph = ({
37
+ const IconTextParagraph = ({
38
38
  children,
39
39
  className,
40
40
  ...rest
41
- }: IcoTextParagraphProps): React.JSX.Element => (
41
+ }: IconTextParagraphProps): React.JSX.Element => (
42
42
  <p className={classNames('ds-ico-text__paragraph', className)} {...rest}>
43
43
  {children}
44
44
  </p>
45
45
  );
46
46
 
47
- const IcoTextIcon = ({
47
+ const IconTextIcon = ({
48
48
  className,
49
49
  color,
50
50
  name,
51
51
  screenReaderText,
52
52
  size = 24,
53
- }: IcoTextIconProps): React.JSX.Element => (
53
+ }: IconTextIconProps): React.JSX.Element => (
54
54
  <Icon
55
55
  name={name}
56
56
  className={classNames('ds-ico-text__icon', className)}
@@ -60,12 +60,12 @@ const IcoTextIcon = ({
60
60
  />
61
61
  );
62
62
 
63
- const IcoTextRoot = ({ children, className }: IcoTextProps): React.JSX.Element => {
63
+ const IconTextRoot = ({ children, className }: IconTextProps): React.JSX.Element => {
64
64
  const iconChildren: React.ReactNode[] = [];
65
65
  const contentChildren: React.ReactNode[] = [];
66
66
 
67
67
  Children.forEach(children, (child) => {
68
- if (isValidElement(child) && child.type === IcoTextIcon) {
68
+ if (isValidElement(child) && child.type === IconTextIcon) {
69
69
  iconChildren.push(child);
70
70
  return;
71
71
  }
@@ -81,13 +81,20 @@ const IcoTextRoot = ({ children, className }: IcoTextProps): React.JSX.Element =
81
81
  );
82
82
  };
83
83
 
84
- IcoTextHeading.displayName = 'IcoText.Heading';
85
- IcoTextParagraph.displayName = 'IcoText.Paragraph';
86
- IcoTextIcon.displayName = 'IcoText.Icon';
87
- IcoTextRoot.displayName = 'IcoText';
84
+ IconTextHeading.displayName = 'IconText.Heading';
85
+ IconTextParagraph.displayName = 'IconText.Paragraph';
86
+ IconTextIcon.displayName = 'IconText.Icon';
87
+ IconTextRoot.displayName = 'IconText';
88
88
 
89
- export const IcoText = Object.assign(IcoTextRoot, {
90
- Heading: IcoTextHeading,
91
- Paragraph: IcoTextParagraph,
92
- Icon: IcoTextIcon,
89
+ export const IconText = Object.assign(IconTextRoot, {
90
+ Heading: IconTextHeading,
91
+ Paragraph: IconTextParagraph,
92
+ Icon: IconTextIcon,
93
93
  });
94
+
95
+ export namespace IconText {
96
+ export type Props = IconTextProps;
97
+ export type HeadingProps = IconTextHeadingProps;
98
+ export type ParagraphProps = IconTextParagraphProps;
99
+ export type IconProps = IconTextIconProps;
100
+ }
@@ -20,6 +20,8 @@ export type ModalProps = {
20
20
  overlayClassName?: string;
21
21
  open?: boolean;
22
22
  portalTarget?: HTMLElement | null;
23
+ // Optional id for the dialog content so external triggers can point aria-controls at it.
24
+ contentId?: string;
23
25
  children?: React.ReactNode;
24
26
  hideCloseButton?: boolean;
25
27
  closeHandler?: ModalContextValue['closeHandler'];
@@ -32,6 +34,7 @@ export const Modal = (props: ModalProps) => {
32
34
  overlayClassName,
33
35
  open,
34
36
  portalTarget,
37
+ contentId,
35
38
  children,
36
39
  closeHandler,
37
40
  hideCloseButton = false,
@@ -46,7 +49,8 @@ export const Modal = (props: ModalProps) => {
46
49
  <Dialog.Root open={open}>
47
50
  <Dialog.Portal container={portalTarget}>
48
51
  <Dialog.Overlay ref={overlayRef} className={classNames('ds-modal__overlay', overlayClassName)}>
49
- <Dialog.Content className={classNames('ds-modal__container', className)}>
52
+ {/* Preserve an optional target for trigger/content relationships such as FilterBar button-to-dialog wiring. */}
53
+ <Dialog.Content id={contentId} className={classNames('ds-modal__container', className)}>
50
54
  {title && <ModalHeader><ModalTitle>{title}</ModalTitle></ModalHeader>}
51
55
  {children}
52
56
  {!hideCloseButton && <ModalCloseButon className="ds-modal__close-button--top-right" />}
@@ -1,17 +1,17 @@
1
- import type { Meta, StoryObj } from '@storybook/react-vite';
2
1
  import {
3
2
  Controls,
4
3
  Heading as DocHeading,
5
- Markdown,
6
4
  Primary as DocPrimary,
5
+ Markdown,
7
6
  Stories,
8
7
  Subtitle,
9
8
  Title,
10
9
  } from '@storybook/addon-docs/blocks';
11
- import { useState } from 'react';
12
- import { fn } from 'storybook/test';
10
+ import type { Meta, StoryObj } from '@storybook/react-vite';
13
11
  import { Dot } from 'Components/dot/Dot';
14
12
  import { Icon } from 'Components/icon/Icon';
13
+ import { useState } from 'react';
14
+ import { fn } from 'storybook/test';
15
15
  import { Tag, type TagColor } from './Tag.js';
16
16
 
17
17
  // ---------------------------------------------------------------------------
@@ -108,8 +108,48 @@ describe('Tag', () => {
108
108
  });
109
109
  });
110
110
 
111
+ describe('onClick', () => {
112
+ test('renders a primary action button when onClick is provided', () => {
113
+ render(<Tag onClick={() => {}}>Clickable</Tag>);
114
+ expect(screen.getByRole('button', { name: 'Clickable' })).toBeInTheDocument();
115
+ });
116
+
117
+ test('uses a custom action label when provided', () => {
118
+ render(<Tag onClick={() => {}} actionLabel="Open filter for Alice Johnson">Alice Johnson</Tag>);
119
+ expect(screen.getByRole('button', { name: 'Open filter for Alice Johnson' })).toBeInTheDocument();
120
+ });
121
+
122
+ test('allows composite parents to remove the primary action from the tab order', () => {
123
+ render(
124
+ <Tag onClick={() => {}} actionButtonTabIndex={-1}>
125
+ Clickable
126
+ </Tag>,
127
+ );
128
+ expect(screen.getByRole('button', { name: 'Clickable' })).toHaveAttribute('tabindex', '-1');
129
+ });
130
+
131
+ test('fires onClick callback when the primary action is clicked', async () => {
132
+ const onClick = vi.fn();
133
+ render(<Tag onClick={onClick}>Clickable</Tag>);
134
+
135
+ await userEvent.click(screen.getByRole('button', { name: 'Clickable' }));
136
+ expect(onClick).toHaveBeenCalledOnce();
137
+ });
138
+
139
+ test('forwards aria-controls and aria-expanded to the primary action button', () => {
140
+ render(
141
+ <Tag onClick={() => {}} ariaControls="filter-modal" ariaExpanded={true}>
142
+ Clickable
143
+ </Tag>,
144
+ );
145
+
146
+ expect(screen.getByRole('button', { name: 'Clickable' })).toHaveAttribute('aria-controls', 'filter-modal');
147
+ expect(screen.getByRole('button', { name: 'Clickable' })).toHaveAttribute('aria-expanded', 'true');
148
+ });
149
+ });
150
+
111
151
  describe('combined slots', () => {
112
- test('renders slotStart, children, slotEnd, and remove button in correct order', () => {
152
+ test('renders non-clickable removable tags through the shared content wrapper', () => {
113
153
  const { container } = render(
114
154
  <Tag
115
155
  slotStart={<span>S</span>}
@@ -122,10 +162,27 @@ describe('Tag', () => {
122
162
 
123
163
  const tag = container.querySelector('.ds-tag')!;
124
164
  const children = Array.from(tag.children);
125
- expect(children[0]).toHaveClass('ds-tag__slot-start');
126
- expect(children[1]).toHaveClass('ds-tag__label');
127
- expect(children[2]).toHaveClass('ds-tag__slot-end');
128
- expect(children[3]).toHaveClass('ds-tag__remove');
165
+ expect(children[0]).toHaveClass('ds-tag__content');
166
+ expect(children[1]).toHaveClass('ds-tag__remove');
167
+
168
+ const contentChildren = Array.from(children[0]!.children);
169
+ expect(contentChildren[0]).toHaveClass('ds-tag__slot-start');
170
+ expect(contentChildren[1]).toHaveClass('ds-tag__label');
171
+ expect(contentChildren[2]).toHaveClass('ds-tag__slot-end');
172
+ });
173
+
174
+ test('renders clickable tags through the same shared content wrapper', () => {
175
+ const { container } = render(
176
+ <Tag
177
+ slotStart={<span>S</span>}
178
+ slotEnd={<span>E</span>}
179
+ onClick={() => {}}
180
+ >
181
+ Label
182
+ </Tag>,
183
+ );
184
+
185
+ expect(container.querySelector('.ds-tag__content.ds-tag__action')).toBeInTheDocument();
129
186
  });
130
187
  });
131
188
  });
@@ -1,5 +1,6 @@
1
1
  import classNames from 'classnames';
2
2
  import { Icon } from 'Components/icon/Icon';
3
+ import type { ButtonHTMLAttributes } from 'react';
3
4
 
4
5
  export type TagColor = 'neutral'
5
6
  | 'orange'
@@ -16,9 +17,21 @@ export type TagProps = {
16
17
  selected?: boolean;
17
18
  slotStart?: React.ReactNode;
18
19
  slotEnd?: React.ReactNode;
20
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
21
+ onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>;
22
+ onFocus?: React.FocusEventHandler<HTMLButtonElement>;
23
+ actionLabel?: string;
24
+ actionButtonTabIndex?: 0 | -1;
25
+ actionRef?: React.Ref<HTMLButtonElement>;
19
26
  onRemove?: () => void;
20
27
  removeLabel?: string;
28
+ onRemoveKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>;
29
+ onRemoveFocus?: React.FocusEventHandler<HTMLButtonElement>;
21
30
  removeButtonTabIndex?: 0 | -1;
31
+ removeButtonRef?: React.Ref<HTMLButtonElement>;
32
+ disabled?: boolean;
33
+ ariaControls?: string;
34
+ ariaExpanded?: ButtonHTMLAttributes<HTMLButtonElement>['aria-expanded'];
22
35
  };
23
36
 
24
37
  export const Tag = ({
@@ -27,18 +40,59 @@ export const Tag = ({
27
40
  selected = false,
28
41
  slotStart,
29
42
  slotEnd,
43
+ onClick,
44
+ onKeyDown,
45
+ onFocus,
46
+ actionLabel,
47
+ actionButtonTabIndex = 0,
48
+ actionRef,
30
49
  onRemove,
31
50
  removeLabel = 'Remove',
51
+ onRemoveKeyDown,
52
+ onRemoveFocus,
32
53
  removeButtonTabIndex = 0,
54
+ removeButtonRef,
55
+ disabled = false,
56
+ ariaControls,
57
+ ariaExpanded,
33
58
  }: TagProps): React.JSX.Element => {
59
+ const tagContent = onClick
60
+ ? (
61
+ <button
62
+ type="button"
63
+ className="ds-tag__content ds-tag__action"
64
+ aria-label={actionLabel}
65
+ aria-controls={ariaControls}
66
+ aria-expanded={ariaExpanded}
67
+ onClick={onClick}
68
+ onKeyDown={onKeyDown}
69
+ onFocus={onFocus}
70
+ tabIndex={actionButtonTabIndex}
71
+ ref={actionRef}
72
+ disabled={disabled}
73
+ >
74
+ {slotStart && <span className="ds-tag__slot-start">{slotStart}</span>}
75
+ <span className="ds-tag__label">{children}</span>
76
+ {slotEnd && <span className="ds-tag__slot-end">{slotEnd}</span>}
77
+ </button>
78
+ )
79
+ : (
80
+ <span className="ds-tag__content">
81
+ {slotStart && <span className="ds-tag__slot-start">{slotStart}</span>}
82
+ <span className="ds-tag__label">{children}</span>
83
+ {slotEnd && <span className="ds-tag__slot-end">{slotEnd}</span>}
84
+ </span>
85
+ );
86
+
34
87
  return (
35
88
  <span className={classNames('ds-tag', `ds-tag--${color}`, {
36
89
  'ds-tag--selected': selected,
90
+ 'ds-tag--interactive': Boolean(onClick),
91
+ 'ds-tag--removable': Boolean(onRemove),
92
+ 'ds-tag--disabled': disabled,
37
93
  })}
38
94
  >
39
- {slotStart && <span className="ds-tag__slot-start">{slotStart}</span>}
40
- <span className="ds-tag__label">{children}</span>
41
- {slotEnd && <span className="ds-tag__slot-end">{slotEnd}</span>}
95
+ {tagContent}
42
96
  {onRemove && (
43
97
  <button
44
98
  type="button"
@@ -48,7 +102,11 @@ export const Tag = ({
48
102
  e.stopPropagation();
49
103
  onRemove();
50
104
  }}
105
+ onKeyDown={onRemoveKeyDown}
106
+ onFocus={onRemoveFocus}
51
107
  tabIndex={removeButtonTabIndex}
108
+ ref={removeButtonRef}
109
+ disabled={disabled}
52
110
  >
53
111
  <Icon name="x" size={12} />
54
112
  </button>
@@ -1,6 +1,5 @@
1
1
  .ds-tag {
2
2
  display: inline-flex;
3
- padding: 0 var(--tag-spacing-horizontal);
4
3
  align-items: center;
5
4
  gap: var(--tag-spacing-gap-horizontal);
6
5
  border-radius: var(--tag-radius);
@@ -13,6 +12,7 @@
13
12
  height: var(--tag-height);
14
13
  font-style: normal;
15
14
  line-height: 150%;
15
+ overflow: visible;
16
16
 
17
17
  &--neutral {
18
18
  color: var(--tag-neutral-color-text);
@@ -60,6 +60,68 @@
60
60
  border-color: var(--tag-selected-color-border);
61
61
  }
62
62
 
63
+ .ds-tag__label {
64
+ overflow: hidden;
65
+ text-overflow: ellipsis;
66
+ }
67
+
68
+ &--disabled {
69
+ opacity: 0.6;
70
+ }
71
+ }
72
+
73
+ .ds-tag__content {
74
+ display: inline-flex;
75
+ align-items: center;
76
+ gap: inherit;
77
+ min-width: 0;
78
+ height: 100%;
79
+ padding: 0 var(--tag-spacing-horizontal);
80
+ border-radius: inherit;
81
+ box-sizing: border-box;
82
+ }
83
+
84
+ .ds-tag__action {
85
+ border: none;
86
+ background: transparent;
87
+ color: inherit;
88
+ font: inherit;
89
+ cursor: pointer;
90
+
91
+ &:focus {
92
+ outline: none;
93
+ }
94
+
95
+ &:focus-visible {
96
+ outline: none;
97
+ background-color: var(--color-grey-100);
98
+ }
99
+
100
+ &:hover {
101
+ background-color: var(--color-grey-100);
102
+ }
103
+
104
+ &:disabled {
105
+ cursor: not-allowed;
106
+ }
107
+ }
108
+
109
+ .ds-tag--removable {
110
+ gap: 0;
111
+
112
+ .ds-tag__label {
113
+ padding: 0 var(--tag-spacing-horizontal) 0 0;
114
+ }
115
+
116
+ .ds-tag__content {
117
+ gap: var(--tag-spacing-horizontal);
118
+ padding-right: 0;
119
+ }
120
+ }
121
+
122
+ .ds-tag:has(.ds-tag__action:focus-visible) {
123
+ outline: var(--focus-border) solid var(--focus-color-focus);
124
+ outline-offset: 2px;
63
125
  }
64
126
 
65
127
  .ds-tag__slot-start {
@@ -68,11 +130,6 @@
68
130
  flex-shrink: 0;
69
131
  }
70
132
 
71
- .ds-tag__label {
72
- overflow: hidden;
73
- text-overflow: ellipsis;
74
- }
75
-
76
133
  .ds-tag__slot-end {
77
134
  display: flex;
78
135
  align-items: center;
@@ -80,19 +137,33 @@
80
137
  }
81
138
 
82
139
  .ds-tag__remove {
83
- display: flex;
140
+ display: inline-flex;
84
141
  align-items: center;
85
142
  justify-content: center;
86
143
  border: none;
87
144
  background: transparent;
145
+ align-self: stretch;
146
+ min-width: calc(var(--tag-height) - (var(--border-weight) * 2));
88
147
  padding: 0;
89
148
  cursor: pointer;
90
149
  color: inherit;
91
- border-radius: 50%;
150
+ border-radius: var(--tag-radius);
92
151
  flex-shrink: 0;
152
+ line-height: 0;
153
+
154
+ &:focus {
155
+ outline: none;
156
+ }
157
+
158
+ &:focus-visible {
159
+ outline: var(--focus-border) solid var(--focus-color-focus);
160
+ outline-offset: 2px;
161
+ border-radius: var(--tag-radius);
162
+ background-color: var(--color-grey-100);
163
+ }
93
164
 
94
165
  &:hover {
95
- background-color: var(--color-grey-200);
166
+ background-color: var(--color-grey-100);
96
167
  }
97
168
 
98
169
  &:disabled {