@drivenets/design-system 0.19.0 → 0.20.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 (110) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/index.cjs +12409 -10548
  3. package/dist/index.d.cts +678 -312
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +679 -313
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +12404 -10550
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +29 -29
  11. package/src/components/ds-checkbox/ds-checkbox.figma.ts +3 -0
  12. package/src/components/ds-checkbox/ds-checkbox.module.scss +102 -20
  13. package/src/components/ds-checkbox/ds-checkbox.tsx +30 -2
  14. package/src/components/ds-checkbox/ds-checkbox.types.ts +10 -0
  15. package/src/components/ds-code-input/collapsed-code-field/collapsed-code-field.module.scss +169 -0
  16. package/src/components/ds-code-input/collapsed-code-field/collapsed-code-field.tsx +92 -0
  17. package/src/components/ds-code-input/collapsed-code-field/index.ts +1 -0
  18. package/src/components/ds-code-input/ds-code-input-panel/ds-code-input-panel.module.scss +110 -0
  19. package/src/components/ds-code-input/ds-code-input-panel/ds-code-input-panel.tsx +61 -0
  20. package/src/components/ds-code-input/ds-code-input-panel/index.ts +1 -0
  21. package/src/components/ds-code-input/ds-code-input.module.scss +25 -0
  22. package/src/components/ds-code-input/ds-code-input.tsx +401 -0
  23. package/src/components/ds-code-input/ds-code-input.types.ts +166 -0
  24. package/src/components/ds-code-input/ds-code-input.utils.ts +94 -0
  25. package/src/components/ds-code-input/ds-code-input.utils.unit.test.ts +166 -0
  26. package/src/components/ds-code-input/index.ts +2 -0
  27. package/src/components/ds-code-input/use-collapsed-viewport.ts +289 -0
  28. package/src/components/ds-code-input/use-editor-host.ts +87 -0
  29. package/src/components/ds-filters-bar/components/ds-filters-bar-builder/ds-filters-bar-builder.ts +11 -0
  30. package/src/components/ds-filters-bar/components/ds-filters-bar-builder/ds-filters-bar-builder.types.ts +41 -0
  31. package/src/components/ds-filters-bar/components/ds-filters-bar-builder/index.ts +1 -0
  32. package/src/components/ds-filters-bar/components/ds-filters-bar-clear-all.ts +11 -0
  33. package/src/components/ds-filters-bar/components/ds-filters-bar-conditions/ds-filters-bar-conditions.ts +11 -0
  34. package/src/components/ds-filters-bar/components/ds-filters-bar-conditions/ds-filters-bar-conditions.types.ts +30 -0
  35. package/src/components/ds-filters-bar/components/ds-filters-bar-conditions/index.ts +1 -0
  36. package/src/components/ds-filters-bar/components/ds-filters-bar-pinned.ts +31 -0
  37. package/src/components/ds-filters-bar/components/ds-filters-bar-query/ds-filters-bar-query.ts +11 -0
  38. package/src/components/ds-filters-bar/components/ds-filters-bar-query/ds-filters-bar-query.types.ts +22 -0
  39. package/src/components/ds-filters-bar/components/ds-filters-bar-query/index.ts +1 -0
  40. package/src/components/ds-filters-bar/components/ds-filters-bar-saved-filters.ts +19 -0
  41. package/src/components/ds-filters-bar/components/ds-filters-bar-search.ts +11 -0
  42. package/src/components/ds-filters-bar/components/ds-filters-bar-summary.ts +11 -0
  43. package/src/components/ds-filters-bar/components/ds-filters-bar-toolbar.ts +11 -0
  44. package/src/components/ds-filters-bar/components/ds-filters-bar-view-switch.ts +11 -0
  45. package/src/components/ds-filters-bar/components/ds-filters-bar-view.ts +11 -0
  46. package/src/components/ds-filters-bar/components/index.ts +11 -0
  47. package/src/components/ds-filters-bar/ds-filters-bar.context.ts +69 -0
  48. package/src/components/ds-filters-bar/ds-filters-bar.tsx +120 -0
  49. package/src/components/ds-filters-bar/ds-filters-bar.types.ts +440 -0
  50. package/src/components/ds-filters-bar/ds-filters-bar.unit.test.ts +216 -0
  51. package/src/components/ds-filters-bar/ds-filters-bar.utils.ts +141 -0
  52. package/src/components/ds-filters-bar/index.ts +1 -0
  53. package/src/components/ds-form-control/ds-form-control.module.scss +18 -0
  54. package/src/components/ds-form-control/ds-form-control.tsx +43 -9
  55. package/src/components/ds-form-control/ds-form-control.types.ts +2 -1
  56. package/src/components/ds-icon/custom-icons/index.ts +2 -0
  57. package/src/components/ds-icon/custom-icons/special-adv-filters.tsx +28 -0
  58. package/src/components/ds-pin-toggle/ds-pin-toggle.figma.ts +27 -0
  59. package/src/components/ds-pin-toggle/ds-pin-toggle.module.scss +41 -0
  60. package/src/components/ds-pin-toggle/ds-pin-toggle.tsx +43 -0
  61. package/src/components/ds-pin-toggle/ds-pin-toggle.types.ts +29 -0
  62. package/src/components/ds-pin-toggle/index.ts +2 -0
  63. package/src/components/ds-popover/components/popover-trigger/index.ts +1 -0
  64. package/src/components/ds-popover/components/popover-trigger/popover-trigger.tsx +15 -0
  65. package/src/components/ds-popover/ds-popover.context.ts +18 -0
  66. package/src/components/ds-popover/ds-popover.hover-intent.ts +65 -0
  67. package/src/components/ds-popover/ds-popover.tsx +163 -41
  68. package/src/components/ds-popover/ds-popover.types.ts +59 -1
  69. package/src/components/ds-popover/ds-popover.utils.ts +60 -0
  70. package/src/components/ds-popover/ds-popover.utils.unit.test.ts +25 -0
  71. package/src/components/ds-saved-filters/components/ds-saved-filters-delete-confirm.tsx +53 -0
  72. package/src/components/ds-saved-filters/components/ds-saved-filters-name-entry.tsx +64 -0
  73. package/src/components/ds-saved-filters/components/ds-saved-filters-name-modal.tsx +50 -0
  74. package/src/components/ds-saved-filters/components/ds-saved-filters-row/ds-saved-filters-row.module.scss +68 -0
  75. package/src/components/ds-saved-filters/components/ds-saved-filters-row/ds-saved-filters-row.tsx +83 -0
  76. package/src/components/ds-saved-filters/components/ds-saved-filters-row/index.ts +1 -0
  77. package/src/components/ds-saved-filters/components/ds-saved-filters-save.tsx +164 -0
  78. package/src/components/ds-saved-filters/components/ds-saved-filters-trigger/ds-saved-filters-trigger.module.scss +21 -0
  79. package/src/components/ds-saved-filters/components/ds-saved-filters-trigger/ds-saved-filters-trigger.tsx +237 -0
  80. package/src/components/ds-saved-filters/components/ds-saved-filters-trigger/index.ts +1 -0
  81. package/src/components/ds-saved-filters/components/index.ts +2 -0
  82. package/src/components/ds-saved-filters/ds-saved-filters.ts +6 -0
  83. package/src/components/ds-saved-filters/ds-saved-filters.types.ts +106 -0
  84. package/src/components/ds-saved-filters/ds-saved-filters.utils.ts +35 -0
  85. package/src/components/ds-saved-filters/hooks/index.ts +1 -0
  86. package/src/components/ds-saved-filters/hooks/use-picker-outside-dismiss.ts +42 -0
  87. package/src/components/ds-saved-filters/index.ts +2 -0
  88. package/src/components/ds-segment-group/ds-segment-group.module.scss +65 -37
  89. package/src/components/ds-segment-group/ds-segment-group.tsx +17 -5
  90. package/src/components/ds-segment-group/ds-segment-group.types.ts +20 -5
  91. package/src/components/ds-segment-group/figma/ds-segment-group-v2-item.figma.ts +109 -0
  92. package/src/components/ds-segment-group/figma/ds-segment-group-v2.figma.ts +31 -0
  93. package/src/components/ds-tag/ds-tag.figma.ts +23 -3
  94. package/src/components/ds-tag/ds-tag.module.scss +71 -7
  95. package/src/components/ds-tag/ds-tag.tsx +115 -14
  96. package/src/components/ds-tag/ds-tag.types.ts +70 -3
  97. package/src/components/ds-tag/ds-tag.unit.test.ts +23 -4
  98. package/src/components/ds-tag/index.ts +2 -2
  99. package/src/components/ds-text-input/ds-input-field-v02.figma.ts +2 -0
  100. package/src/components/ds-toggle/ds-toggle.tsx +19 -15
  101. package/src/components/ds-toggle/ds-toggle.types.ts +6 -0
  102. package/src/components/ds-toggle-filter-data/ds-toggle-filter-data.module.scss +69 -0
  103. package/src/components/ds-toggle-filter-data/ds-toggle-filter-data.tsx +55 -0
  104. package/src/components/ds-toggle-filter-data/ds-toggle-filter-data.types.ts +43 -0
  105. package/src/components/ds-toggle-filter-data/index.ts +1 -0
  106. package/src/components/ds-tooltip/ds-tooltip.module.scss +8 -0
  107. package/src/components/ds-tooltip/ds-tooltip.tsx +1 -1
  108. package/src/components/ds-tree/ds-tree.tsx +10 -10
  109. package/src/components/ds-tree/ds-tree.types.ts +40 -7
  110. package/src/index.ts +2 -0
@@ -1,5 +1,7 @@
1
1
  @use '../../styles/typography';
2
2
 
3
+ $warning-dot-size: 8px;
4
+
3
5
  .tag {
4
6
  --transition-duration: 200ms;
5
7
 
@@ -19,6 +21,10 @@
19
21
  transition-timing-function: ease-out;
20
22
  transition-duration: var(--transition-duration);
21
23
 
24
+ &.round {
25
+ border-radius: 9999px;
26
+ }
27
+
22
28
  &.clickable {
23
29
  cursor: pointer;
24
30
 
@@ -40,7 +46,8 @@
40
46
  outline-color: var(--border-action-primary);
41
47
  }
42
48
 
43
- .deleteButton {
49
+ .deleteButton,
50
+ .expandButton {
44
51
  outline-color: var(--border-action-primary);
45
52
  }
46
53
  }
@@ -65,13 +72,20 @@
65
72
 
66
73
  .label,
67
74
  .key,
68
- .value {
75
+ .value,
76
+ .queryLabel,
77
+ .queryValue {
69
78
  color: var(--font-disabled);
70
79
  }
71
80
 
72
- .variantIcon {
81
+ .variantIcon,
82
+ .querySeparator {
73
83
  color: var(--font-disabled);
74
84
  }
85
+
86
+ .warningDot {
87
+ background: var(--icon-disabled);
88
+ }
75
89
  }
76
90
 
77
91
  .deleteButton {
@@ -93,6 +107,22 @@
93
107
  }
94
108
  }
95
109
 
110
+ .expandButton {
111
+ flex-shrink: 0;
112
+ display: flex;
113
+ margin-left: var(--3xs);
114
+ outline-color: var(--border-action-secondary);
115
+ user-select: none;
116
+ transition-property: background, color, outline-color;
117
+ transition-timing-function: ease-out;
118
+ transition-duration: var(--transition-duration);
119
+ color: var(--icon-action);
120
+
121
+ &:hover {
122
+ background: var(--background-action-secondary-hover);
123
+ }
124
+ }
125
+
96
126
  &:hover:not(.disabled) .deleteButton,
97
127
  &:focus-within:not(.disabled) .deleteButton,
98
128
  &.selected:not(.disabled) .deleteButton {
@@ -107,17 +137,20 @@
107
137
  @include typography.ellipsis;
108
138
  }
109
139
 
110
- .keyValue {
140
+ .segments {
111
141
  min-width: 0;
112
142
  }
113
143
 
114
144
  .key {
115
145
  flex-shrink: 0;
116
146
  color: var(--font-main);
147
+ }
117
148
 
118
- &::after {
119
- content: ':';
120
- }
149
+ // `key-value` renders its colon as generated content rather than text so the tag's text
150
+ // content stays the bare label — `getByText('Category')` keeps working for consumers.
151
+ // Scoped to its own class because `operator-filter` shares `.key` and takes no colon.
152
+ .keyColon::after {
153
+ content: ':';
121
154
  }
122
155
 
123
156
  .value {
@@ -126,6 +159,37 @@
126
159
  color: var(--font-secondary);
127
160
  }
128
161
 
162
+ .operatorSlot {
163
+ flex-shrink: 0;
164
+ display: flex;
165
+ align-items: center;
166
+ }
167
+
168
+ .queryLabel {
169
+ flex-shrink: 0;
170
+ color: var(--font-main);
171
+ }
172
+
173
+ .querySeparator {
174
+ flex-shrink: 0;
175
+ color: var(--icon-tertiary);
176
+ }
177
+
178
+ .queryValue {
179
+ @include typography.ellipsis;
180
+
181
+ color: var(--font-main);
182
+ }
183
+
184
+ .warningDot {
185
+ flex-shrink: 0;
186
+ width: $warning-dot-size;
187
+ height: $warning-dot-size;
188
+ margin-left: var(--3xs);
189
+ border-radius: 50%;
190
+ background: var(--icon-warning-light);
191
+ }
192
+
129
193
  .icon {
130
194
  flex-shrink: 0;
131
195
  display: flex;
@@ -16,12 +16,15 @@ const DsTag = ({
16
16
  className,
17
17
  style = {},
18
18
  size = 'medium',
19
+ shape = 'default',
19
20
  selected = false,
20
21
  variant = 'default',
22
+ warning = false,
21
23
  disabled = false,
22
24
  locale = {},
23
25
  onClick,
24
26
  onDelete,
27
+ onExpandClick,
25
28
  slots,
26
29
  ...rest
27
30
  }: DsTagProps) => {
@@ -32,6 +35,7 @@ const DsTag = ({
32
35
  [styles.selected]: selected && !disabled,
33
36
  [styles.include]: variant === 'include',
34
37
  [styles.exclude]: variant === 'exclude',
38
+ [styles.round]: shape === 'round',
35
39
  [styles.disabled]: disabled,
36
40
  },
37
41
  className,
@@ -65,6 +69,26 @@ const DsTag = ({
65
69
  }
66
70
  };
67
71
 
72
+ const handleExpandClick = (event: MouseEvent<HTMLElement>) => {
73
+ event.stopPropagation();
74
+ onExpandClick?.(event);
75
+ };
76
+
77
+ const handleKeyDownExpand = (event: KeyboardEvent<HTMLElement>) => {
78
+ event.stopPropagation();
79
+
80
+ if (onExpandClick && (event.key === 'Enter' || event.key === ' ')) {
81
+ event.preventDefault();
82
+ onExpandClick(event);
83
+ }
84
+ };
85
+
86
+ // The operator slot owns a fully-wired control, so neither its clicks nor its keystrokes may
87
+ // reach the tag's own click / delete keyboard shortcuts.
88
+ const stopPropagation = (event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>) => {
89
+ event.stopPropagation();
90
+ };
91
+
68
92
  const handleClick = (event: MouseEvent<HTMLElement>) => {
69
93
  if (onClick) {
70
94
  event.preventDefault();
@@ -82,6 +106,83 @@ const DsTag = ({
82
106
  iconVariant = 'do_not_disturb_on';
83
107
  }
84
108
 
109
+ const warningAriaLabel = locale.warningAriaLabel ?? 'Warning';
110
+
111
+ // `role="button"` makes the subtree presentational and the root's own `aria-label` replaces any
112
+ // name computed from it, so the dot's `role="img"` name is pruned on an interactive tag. Fold it
113
+ // into the root name instead, and keep the dot named for the non-interactive case.
114
+ const rootAriaLabel =
115
+ typeof label === 'string' ? (warning ? `${label}, ${warningAriaLabel}` : label) : undefined;
116
+
117
+ const isSmall = size === 'small';
118
+ const regularVariant = isSmall ? 'body-xs-reg' : 'body-sm-reg';
119
+ const mediumVariant = isSmall ? 'body-xs-md' : 'body-sm-md';
120
+ const semiBoldVariant = isSmall ? 'body-xs-semi-bold' : 'body-sm-semi-bold';
121
+
122
+ const renderContent = () => {
123
+ if (variant === 'key-value') {
124
+ return (
125
+ <DsStack direction="row" alignItems="center" gap="var(--3xs)" className={styles.segments}>
126
+ <DsTypography variant={mediumVariant} className={classNames(styles.key, styles.keyColon)}>
127
+ {label}
128
+ </DsTypography>
129
+ <DsTypography variant={regularVariant} className={styles.value}>
130
+ {value}
131
+ </DsTypography>
132
+ </DsStack>
133
+ );
134
+ }
135
+
136
+ if (variant === 'operator-filter') {
137
+ const operatorSlot = slots?.operator;
138
+
139
+ return (
140
+ <DsStack direction="row" alignItems="center" gap="var(--3xs)" className={styles.segments}>
141
+ <DsTypography variant={mediumVariant} className={styles.key}>
142
+ {label}
143
+ </DsTypography>
144
+ {operatorSlot !== undefined &&
145
+ operatorSlot !== null && (
146
+ // The wrapper is a pure event boundary with no semantics of its own; the slotted
147
+ // control keeps its own. `role="presentation"` is what satisfies
148
+ // jsx-a11y/no-static-element-interactions for a span carrying these handlers.
149
+ <span
150
+ role="presentation"
151
+ className={styles.operatorSlot}
152
+ onClick={stopPropagation}
153
+ onKeyDown={stopPropagation}
154
+ >
155
+ {operatorSlot}
156
+ </span>
157
+ )}
158
+ <DsTypography variant={regularVariant} className={styles.value}>
159
+ {value}
160
+ </DsTypography>
161
+ </DsStack>
162
+ );
163
+ }
164
+
165
+ if (variant === 'query-filter') {
166
+ return (
167
+ <DsStack direction="row" alignItems="center" gap="var(--4xs)" className={styles.segments}>
168
+ <DsTypography variant={regularVariant} className={styles.queryLabel}>
169
+ {label}
170
+ </DsTypography>
171
+ <DsIcon icon="keyboard_arrow_right" size="tiny" aria-hidden className={styles.querySeparator} />
172
+ <DsTypography variant={semiBoldVariant} className={styles.queryValue}>
173
+ {value}
174
+ </DsTypography>
175
+ </DsStack>
176
+ );
177
+ }
178
+
179
+ return (
180
+ <DsTypography variant={regularVariant} className={styles.label}>
181
+ {label}
182
+ </DsTypography>
183
+ );
184
+ };
185
+
85
186
  return (
86
187
  <div
87
188
  ref={ref as Ref<HTMLDivElement>}
@@ -91,7 +192,7 @@ const DsTag = ({
91
192
  onKeyDown={disabled ? undefined : handleKeyDownTag}
92
193
  role={onClick || onDelete ? 'button' : undefined}
93
194
  tabIndex={disabled || (!onClick && !onDelete) ? undefined : 0}
94
- aria-label={typeof label === 'string' ? label : undefined}
195
+ aria-label={rootAriaLabel}
95
196
  aria-pressed={onClick && selected && !disabled ? 'true' : undefined}
96
197
  aria-disabled={disabled}
97
198
  {...rest}
@@ -104,19 +205,19 @@ const DsTag = ({
104
205
  ))}
105
206
  </span>
106
207
  )}
107
- {variant === 'key-value' ? (
108
- <DsStack direction="row" alignItems="center" gap="var(--3xs)" className={styles.keyValue}>
109
- <DsTypography variant={size === 'small' ? 'body-xs-md' : 'body-sm-md'} className={styles.key}>
110
- {label}
111
- </DsTypography>
112
- <DsTypography variant={size === 'small' ? 'body-xs-reg' : 'body-sm-reg'} className={styles.value}>
113
- {value}
114
- </DsTypography>
115
- </DsStack>
116
- ) : (
117
- <DsTypography variant={size === 'small' ? 'body-xs-reg' : 'body-sm-reg'} className={styles.label}>
118
- {label}
119
- </DsTypography>
208
+ {renderContent()}
209
+ {warning && <span role="img" aria-label={warningAriaLabel} className={styles.warningDot} />}
210
+ {onExpandClick && !disabled && (
211
+ <button
212
+ type="button"
213
+ className={styles.expandButton}
214
+ onClick={handleExpandClick}
215
+ onKeyDown={handleKeyDownExpand}
216
+ aria-label={locale.expandAriaLabel ?? 'Expand'}
217
+ tabIndex={0}
218
+ >
219
+ <DsIcon icon="keyboard_arrow_down" size="tiny" aria-hidden />
220
+ </button>
120
221
  )}
121
222
  {onDelete && !disabled && (
122
223
  <button
@@ -3,9 +3,19 @@ import type { CSSProperties, KeyboardEvent, MouseEvent, ReactNode, Ref } from 'r
3
3
  export const tagSizes = ['medium', 'small'] as const;
4
4
  export type TagSize = (typeof tagSizes)[number];
5
5
 
6
- export const tagVariants = ['default', 'include', 'exclude', 'key-value'] as const;
6
+ export const tagVariants = [
7
+ 'default',
8
+ 'include',
9
+ 'exclude',
10
+ 'key-value',
11
+ 'operator-filter',
12
+ 'query-filter',
13
+ ] as const;
7
14
  export type TagVariant = (typeof tagVariants)[number];
8
15
 
16
+ export const tagShapes = ['default', 'round'] as const;
17
+ export type TagShape = (typeof tagShapes)[number];
18
+
9
19
  interface DsTagBaseProps {
10
20
  /**
11
21
  * Ref to the tag element
@@ -27,6 +37,14 @@ interface DsTagBaseProps {
27
37
  * aria-label for the delete button (shown when `onDelete` is provided).
28
38
  */
29
39
  deleteAriaLabel?: string;
40
+ /**
41
+ * Accessible name for the warning dot (shown when `warning` is set).
42
+ */
43
+ warningAriaLabel?: string;
44
+ /**
45
+ * Accessible name for the expand chevron (shown when `onExpandClick` is provided).
46
+ */
47
+ expandAriaLabel?: string;
30
48
  };
31
49
  /**
32
50
  * Additional styles to apply to the component
@@ -40,16 +58,33 @@ interface DsTagBaseProps {
40
58
  * Callback function when the delete icon is clicked
41
59
  */
42
60
  onDelete?: (event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>) => void;
61
+ /**
62
+ * Callback function when the expand chevron is clicked. Providing a handler is what renders the
63
+ * chevron — the tag keeps no expanded state of its own, so the consumer owns whatever the
64
+ * chevron reveals.
65
+ */
66
+ onExpandClick?: (event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>) => void;
43
67
  /**
44
68
  * Size of the tag
45
69
  * @default 'medium'
46
70
  */
47
71
  size?: TagSize;
72
+ /**
73
+ * Border radius of the tag: `'default'` (4px) or `'round'` (pill)
74
+ * @default 'default'
75
+ */
76
+ shape?: TagShape;
48
77
  /**
49
78
  * Whether the tag is in a selected/pressed state
50
79
  * @default false
51
80
  */
52
81
  selected?: boolean;
82
+ /**
83
+ * Whether to render a trailing warning dot. Copy and any tooltip around it belong to the
84
+ * consumer; only the dot's accessible name is configurable, via `locale.warningAriaLabel`.
85
+ * @default false
86
+ */
87
+ warning?: boolean;
53
88
  /**
54
89
  * Whether the tag is disabled
55
90
  * @default false
@@ -64,6 +99,14 @@ interface DsTagBaseProps {
64
99
  * Icon to display at the start of the input
65
100
  */
66
101
  icon?: ReactNode;
102
+ /**
103
+ * Fully-wired operator trigger (for example a `DsSelect`) rendered between `label` and
104
+ * `value`. Honoured only by `variant="operator-filter"` and ignored by every other variant.
105
+ *
106
+ * The tag cannot disable an arbitrary node, so `disabled` does not reach the slotted
107
+ * control — pass `disabled` to it yourself.
108
+ */
109
+ operator?: ReactNode;
67
110
  };
68
111
  }
69
112
 
@@ -79,9 +122,33 @@ interface DsTagStandardProps extends DsTagBaseProps {
79
122
  interface DsTagKeyValueProps extends DsTagBaseProps {
80
123
  variant: 'key-value';
81
124
  /**
82
- * Secondary-colored value rendered after the key (`label`). Required for the `key-value` variant.
125
+ * Secondary-colored value rendered after the key (`label`) and its `:` separator. The key
126
+ * carries the emphasis. Required for the `key-value` variant.
127
+ */
128
+ value: ReactNode;
129
+ }
130
+
131
+ interface DsTagOperatorFilterProps extends DsTagBaseProps {
132
+ variant: 'operator-filter';
133
+ /**
134
+ * Value rendered last, after `label` and the `slots.operator` trigger that sits between them.
135
+ * Required for the `operator-filter` variant.
136
+ */
137
+ value: ReactNode;
138
+ }
139
+
140
+ interface DsTagQueryFilterProps extends DsTagBaseProps {
141
+ variant: 'query-filter';
142
+ /**
143
+ * Emphasized value rendered after `label` and a `keyboard_arrow_right` separator. Emphasis is
144
+ * inverted compared to `key-value`: the value is the strong segment and `label` is regular.
145
+ * Required for the `query-filter` variant.
83
146
  */
84
147
  value: ReactNode;
85
148
  }
86
149
 
87
- export type DsTagProps = DsTagStandardProps | DsTagKeyValueProps;
150
+ export type DsTagProps =
151
+ | DsTagStandardProps
152
+ | DsTagKeyValueProps
153
+ | DsTagOperatorFilterProps
154
+ | DsTagQueryFilterProps;
@@ -1,11 +1,17 @@
1
1
  /* eslint-disable vitest/expect-expect */
2
2
  import type { ReactNode } from 'react';
3
3
  import { describe, expectTypeOf, it } from 'vitest';
4
- import type { DsTagProps, TagVariant } from './ds-tag.types';
4
+ import type { DsTagProps, TagShape, TagVariant } from './ds-tag.types';
5
5
 
6
6
  describe('DsTagProps discriminated union', () => {
7
- it('includes key-value in the variant union', () => {
8
- expectTypeOf<TagVariant>().toEqualTypeOf<'default' | 'include' | 'exclude' | 'key-value'>();
7
+ it('includes key-value and the filter variants in the variant union', () => {
8
+ expectTypeOf<TagVariant>().toEqualTypeOf<
9
+ 'default' | 'include' | 'exclude' | 'key-value' | 'operator-filter' | 'query-filter'
10
+ >();
11
+ });
12
+
13
+ it('exposes default and round shapes', () => {
14
+ expectTypeOf<TagShape>().toEqualTypeOf<'default' | 'round'>();
9
15
  });
10
16
 
11
17
  it('allows standard variants without a value', () => {
@@ -20,13 +26,26 @@ describe('DsTagProps discriminated union', () => {
20
26
  expectTypeOf<{ label: string; variant: 'key-value' }>().not.toExtend<DsTagProps>();
21
27
  });
22
28
 
29
+ it('requires a value for the operator-filter variant', () => {
30
+ expectTypeOf<{ label: string; variant: 'operator-filter'; value: string }>().toExtend<DsTagProps>();
31
+ expectTypeOf<{ label: string; variant: 'operator-filter' }>().not.toExtend<DsTagProps>();
32
+ });
33
+
34
+ it('requires a value for the query-filter variant', () => {
35
+ expectTypeOf<{ label: string; variant: 'query-filter'; value: string }>().toExtend<DsTagProps>();
36
+ expectTypeOf<{ label: string; variant: 'query-filter' }>().not.toExtend<DsTagProps>();
37
+ });
38
+
23
39
  it('forbids a value on standard variants', () => {
24
40
  expectTypeOf<{ label: string; value: string }>().not.toExtend<DsTagProps>();
25
41
  expectTypeOf<{ label: string; variant: 'default'; value: string }>().not.toExtend<DsTagProps>();
26
42
  expectTypeOf<{ label: string; variant: 'include'; value: string }>().not.toExtend<DsTagProps>();
43
+ expectTypeOf<{ label: string; variant: 'exclude'; value: string }>().not.toExtend<DsTagProps>();
27
44
  });
28
45
 
29
- it('types the key-value value as ReactNode', () => {
46
+ it('types every value-bearing variant as ReactNode', () => {
30
47
  expectTypeOf<Extract<DsTagProps, { variant: 'key-value' }>['value']>().toEqualTypeOf<ReactNode>();
48
+ expectTypeOf<Extract<DsTagProps, { variant: 'operator-filter' }>['value']>().toEqualTypeOf<ReactNode>();
49
+ expectTypeOf<Extract<DsTagProps, { variant: 'query-filter' }>['value']>().toEqualTypeOf<ReactNode>();
31
50
  });
32
51
  });
@@ -1,3 +1,3 @@
1
1
  export { default as DsTag } from './ds-tag';
2
- export { tagSizes, tagVariants } from './ds-tag.types';
3
- export type { TagSize, TagVariant, DsTagProps } from './ds-tag.types';
2
+ export { tagShapes, tagSizes, tagVariants } from './ds-tag.types';
3
+ export type { TagShape, TagSize, TagVariant, DsTagProps } from './ds-tag.types';
@@ -24,6 +24,7 @@ const textInfo: FieldInfo = {
24
24
  const registry: Record<string, FieldInfo> = {
25
25
  text: textInfo,
26
26
  textArea: { component: 'DsTextarea', fcMember: 'Textarea' },
27
+ code: { component: 'DsCodeInput', fcMember: 'CodeInput', size: true, large: true, readOnly: true },
27
28
  number: { component: 'DsNumberInput', fcMember: 'NumberInput', size: true, readOnly: true },
28
29
  password: { component: 'DsPasswordInput', fcMember: 'PasswordInput', size: true, readOnly: true },
29
30
  select: {
@@ -75,6 +76,7 @@ const inputType =
75
76
  ? part.getEnum('inputType', {
76
77
  text: 'text',
77
78
  'text-area': 'textArea',
79
+ code: 'code',
78
80
  select: 'select',
79
81
  password: 'password',
80
82
  number: 'number',
@@ -21,6 +21,7 @@ const DsToggle = ({
21
21
  size = 'default',
22
22
  children,
23
23
  checked,
24
+ 'aria-label': ariaLabel,
24
25
  ...rest
25
26
  }: DsToggleProps) => {
26
27
  const isSmall = size === 'small';
@@ -58,21 +59,24 @@ const DsToggle = ({
58
59
  <DsIcon icon="close" size={iconSize} className={closeIconClass} />
59
60
  </Switch.Control>
60
61
 
61
- <Switch.Label>
62
- {label ? (
63
- <>
64
- <DsTypography variant={isSmall ? 'body-xs-md' : 'body-sm-md'}>{label}</DsTypography>
65
- {!!labelInfo && (
66
- <DsTypography variant="body-xs-reg" className={styles.labelInfo}>
67
- {labelInfo}
68
- </DsTypography>
69
- )}
70
- </>
71
- ) : (
72
- children
73
- )}
74
- </Switch.Label>
75
- <Switch.HiddenInput className={styles.hiddenInput} />
62
+ {/* An empty Switch.Label would still be the input's aria-labelledby target and override aria-label. */}
63
+ {(label || children) && (
64
+ <Switch.Label>
65
+ {label ? (
66
+ <>
67
+ <DsTypography variant={isSmall ? 'body-xs-md' : 'body-sm-md'}>{label}</DsTypography>
68
+ {!!labelInfo && (
69
+ <DsTypography variant="body-xs-reg" className={styles.labelInfo}>
70
+ {labelInfo}
71
+ </DsTypography>
72
+ )}
73
+ </>
74
+ ) : (
75
+ children
76
+ )}
77
+ </Switch.Label>
78
+ )}
79
+ <Switch.HiddenInput className={styles.hiddenInput} aria-label={ariaLabel} />
76
80
  </Switch.Root>
77
81
  );
78
82
  };
@@ -52,6 +52,12 @@ export type DsToggleProps = {
52
52
  * Whether the toggle is disabled
53
53
  */
54
54
  disabled?: boolean;
55
+
56
+ /**
57
+ * Accessible name for a toggle without a visible `label` or `children`.
58
+ * Ignored by assistive tech when a visible label is rendered.
59
+ */
60
+ 'aria-label'?: string;
55
61
  } & (
56
62
  | {
57
63
  /**
@@ -0,0 +1,69 @@
1
+ .pill {
2
+ box-sizing: border-box;
3
+ display: inline-flex;
4
+ align-items: center;
5
+ gap: var(--4xs);
6
+ height: var(--lg);
7
+ padding: var(--4xs) var(--xs);
8
+ border: 1px solid var(--border);
9
+ border-radius: 9999px;
10
+ background: var(--background);
11
+ color: var(--font-main);
12
+ cursor: pointer;
13
+ transition-property: background, border-color, box-shadow, color;
14
+ transition-timing-function: ease-out;
15
+ transition-duration: 0.2s;
16
+
17
+ &:hover {
18
+ background: var(--background-action-hover-weak);
19
+ border-color: var(--border-field-hover);
20
+ }
21
+
22
+ &:focus-visible {
23
+ outline: none;
24
+ border-color: var(--border-field-hover);
25
+ background: var(--background-action-hover-weak);
26
+ box-shadow: inset 0 0 0 1px var(--border-field-hover);
27
+ }
28
+
29
+ &.active {
30
+ border-color: var(--border-action-primary);
31
+ background: var(--background-secondary-hover);
32
+
33
+ &:hover,
34
+ &:focus-visible {
35
+ border-color: var(--border-action-primary-hover);
36
+ background: var(--background-action-secondary-hover);
37
+ }
38
+
39
+ &:focus-visible {
40
+ box-shadow: inset 0 0 0 1px var(--border-action-primary-hover);
41
+ }
42
+
43
+ &:disabled {
44
+ border-color: var(--border-disabled);
45
+ background: var(--background-secondary);
46
+ }
47
+ }
48
+
49
+ &:disabled {
50
+ border-color: var(--border-disabled);
51
+ background: var(--background-action-hover-weak);
52
+ color: var(--font-disabled);
53
+ box-shadow: none;
54
+ cursor: not-allowed;
55
+
56
+ .value {
57
+ color: var(--font-disabled);
58
+ }
59
+ }
60
+ }
61
+
62
+ .label,
63
+ .value {
64
+ flex-shrink: 0;
65
+ }
66
+
67
+ .value {
68
+ color: var(--font-secondary);
69
+ }
@@ -0,0 +1,55 @@
1
+ import type { MouseEvent } from 'react';
2
+ import classNames from 'classnames';
3
+ import styles from './ds-toggle-filter-data.module.scss';
4
+ import type { DsToggleFilterDataProps } from './ds-toggle-filter-data.types';
5
+ import { DsTypography } from '../ds-typography';
6
+
7
+ /**
8
+ * @summary Data pill that toggles on and off, pairing a label with its value, for filter rows
9
+ * above a table or list.
10
+ *
11
+ * Internal to the filters component and not exported from the package.
12
+ *
13
+ * A real `<button type="button">`, so `aria-pressed`, Enter/Space activation, the disabled state
14
+ * and the focus ring are all native. Selection is controlled: the pill reports the next `active`
15
+ * value through `onActiveChange` and never toggles itself.
16
+ */
17
+ const DsToggleFilterData = ({
18
+ ref,
19
+ label,
20
+ value,
21
+ active,
22
+ disabled = false,
23
+ className,
24
+ style,
25
+ onActiveChange,
26
+ onClick,
27
+ }: DsToggleFilterDataProps) => {
28
+ const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
29
+ onClick?.(event);
30
+ onActiveChange?.(!active);
31
+ };
32
+
33
+ return (
34
+ <button
35
+ ref={ref}
36
+ type="button"
37
+ className={classNames(styles.pill, { [styles.active]: active }, className)}
38
+ style={style}
39
+ disabled={disabled}
40
+ aria-pressed={active}
41
+ onClick={handleClick}
42
+ >
43
+ <DsTypography variant="body-xs-md" className={styles.label}>
44
+ {label}
45
+ </DsTypography>
46
+ <DsTypography variant="body-xs-reg" className={styles.value}>
47
+ {value}
48
+ </DsTypography>
49
+ </button>
50
+ );
51
+ };
52
+
53
+ DsToggleFilterData.displayName = 'DsToggleFilterData';
54
+
55
+ export default DsToggleFilterData;