@drivenets/design-system 0.8.0 → 0.9.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 (59) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/index.cjs +1559 -1074
  3. package/dist/index.d.cts +559 -135
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +559 -135
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1547 -1076
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +12 -11
  11. package/src/components/ds-autocomplete/ds-autocomplete.module.scss +0 -1
  12. package/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss +25 -9
  13. package/src/components/ds-button/versions/ds-button-new/ds-button-new.tsx +5 -5
  14. package/src/components/ds-button/versions/ds-button-new/ds-button-new.types.ts +11 -1
  15. package/src/components/ds-button/versions/ds-button-new/index.ts +4 -1
  16. package/src/components/ds-button-v3/ds-button-v3.module.scss +391 -0
  17. package/src/components/ds-button-v3/ds-button-v3.tsx +55 -0
  18. package/src/components/ds-button-v3/ds-button-v3.types.ts +49 -0
  19. package/src/components/ds-button-v3/index.ts +2 -0
  20. package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx +1 -1
  21. package/src/components/ds-comments-drawer/ds-comments-drawer.tsx +3 -9
  22. package/src/components/ds-date-picker/ds-date-picker.module.scss +10 -0
  23. package/src/components/ds-date-picker/ds-date-picker.tsx +4 -2
  24. package/src/components/ds-date-range-picker/ds-date-range-picker.tsx +2 -2
  25. package/src/components/ds-drawer/ds-drawer.tsx +6 -9
  26. package/src/components/ds-drawer/ds-drawer.types.ts +4 -2
  27. package/src/components/ds-form-control/ds-form-control.tsx +2 -3
  28. package/src/components/ds-panel/ds-panel.module.scss +4 -1
  29. package/src/components/ds-panel/ds-panel.tsx +8 -3
  30. package/src/components/ds-panel/ds-panel.types.ts +8 -1
  31. package/src/components/ds-panel/index.ts +4 -1
  32. package/src/components/ds-select/ds-select.module.scss +4 -1
  33. package/src/components/ds-split-button/ds-split-button.module.scss +99 -0
  34. package/src/components/ds-split-button/ds-split-button.tsx +49 -0
  35. package/src/components/ds-split-button/ds-split-button.types.ts +25 -0
  36. package/src/components/ds-split-button/index.ts +10 -0
  37. package/src/components/ds-stack/ds-stack.module.scss +4 -0
  38. package/src/components/ds-stack/ds-stack.tsx +35 -0
  39. package/src/components/ds-stack/ds-stack.types.ts +19 -0
  40. package/src/components/ds-stack/index.ts +18 -0
  41. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +2 -2
  42. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +5 -8
  43. package/src/components/ds-table/ds-table.module.scss +6 -0
  44. package/src/components/ds-table/ds-table.tsx +3 -0
  45. package/src/components/ds-time-picker/ds-time-picker.module.scss +11 -0
  46. package/src/components/ds-time-picker/ds-time-picker.tsx +32 -12
  47. package/src/components/ds-time-picker/ds-time-picker.types.ts +6 -0
  48. package/src/index.ts +4 -0
  49. package/src/styles/_breakpoints.module.scss +5 -0
  50. package/src/styles/_responsive.scss +13 -0
  51. package/src/styles/_variables.scss +2 -0
  52. package/src/styles/shared/_button.scss +2 -0
  53. package/src/styles/shared/_input.scss +3 -40
  54. package/src/utils/responsive.browser.test.ts +94 -0
  55. package/src/utils/responsive.ts +79 -0
  56. package/src/utils/responsive.unit.test.ts +54 -0
  57. package/src/utils/type-utils.ts +1 -0
  58. package/src/utils/use-controlled.browser.test.ts +6 -6
  59. package/src/utils/use-controlled.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drivenets/design-system",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.cts",
@@ -54,7 +54,8 @@
54
54
  "@tanstack/react-virtual": "^3.13.23",
55
55
  "@zag-js/react": "^1.37.0",
56
56
  "@zag-js/steps": "^1.37.0",
57
- "classnames": "^2.5.1"
57
+ "classnames": "^2.5.1",
58
+ "csstype": "^3.2.3"
58
59
  },
59
60
  "peerDependencies": {
60
61
  "@internationalized/date": "^3.0.0",
@@ -67,13 +68,14 @@
67
68
  "@eslint/compat": "^2.0.3",
68
69
  "@faker-js/faker": "^10.3.0",
69
70
  "@hookform/resolvers": "^5.2.2",
70
- "@rolldown/plugin-babel": "^0.2.1",
71
+ "@rolldown/plugin-babel": "^0.2.2",
71
72
  "@storybook/addon-a11y": "^10.2.19",
72
73
  "@storybook/addon-docs": "^10.2.19",
73
74
  "@storybook/addon-vitest": "^10.2.19",
74
75
  "@storybook/react-vite": "^10.2.19",
75
76
  "@tanstack/react-query": "^5.90.21",
76
77
  "@tanstack/react-router": "^1.166.2",
78
+ "@tsdown/css": "^0.21.6",
77
79
  "@types/eslint-plugin-jsx-a11y": "^6.10.1",
78
80
  "@types/react": "^19.2.14",
79
81
  "@types/react-dom": "^19.2.3",
@@ -88,29 +90,28 @@
88
90
  "eslint-plugin-storybook": "^10.2.19",
89
91
  "mockdate": "^3.0.5",
90
92
  "playwright": "^1.58.2",
91
- "postcss": "^8.5.8",
92
- "postcss-modules": "^6.0.1",
93
93
  "publint": "^0.3.18",
94
94
  "react": "^19.2.4",
95
95
  "react-dom": "^19.2.4",
96
96
  "react-hook-form": "^7.71.2",
97
- "rollup-plugin-sass": "^1.15.3",
98
97
  "sass-embedded": "^1.97.3",
99
98
  "storybook": "^10.2.19",
100
- "tsdown": "^0.21.0",
99
+ "tsdown": "^0.21.6",
101
100
  "typed-scss-modules": "^8.1.1",
102
101
  "typescript": "^5.9.3",
103
102
  "typescript-plugin-css-modules": "^5.2.0",
104
- "vite": "^8.0.0",
103
+ "vite": "8.0.5",
105
104
  "vitest": "^4.1.0",
106
105
  "vitest-browser-react": "^2.1.0",
107
106
  "zod": "^4.3.6",
108
- "@drivenets/vite-plugin-design-system": "0.0.6",
109
- "@drivenets/eslint-plugin-internal": "0.0.0"
107
+ "@drivenets/eslint-plugin-internal": "0.0.0",
108
+ "@drivenets/vite-plugin-design-system": "0.0.7"
110
109
  },
111
110
  "scripts": {
112
111
  "install:playwright": "pnpm exec playwright install chromium --with-deps",
113
- "lint": "pnpm generate-scss-dts && eslint --max-warnings=0 .; EXIT_CODE=$?; rm -rf .scss-dts; exit $EXIT_CODE",
112
+ "lint": "pnpm generate-scss-dts && eslint --max-warnings=0 ${ESLINT_ARGS-} .; EXIT_CODE=$?; rm -rf .scss-dts; exit $EXIT_CODE",
113
+ "//": "ESLINT_ARGS is used to pass arguments to eslint. We're using it so we don't need to duplicate the lint script.",
114
+ "lint:fix": "ESLINT_ARGS=--fix pnpm lint",
114
115
  "typecheck": "pnpm generate-scss-dts && tsc; EXIT_CODE=$?; rm -rf .scss-dts; exit $EXIT_CODE",
115
116
  "generate-scss-dts": "typed-scss-modules ./src -o ./.scss-dts --nameFormat all --exportType default > /dev/null 2>&1",
116
117
  "storybook": "storybook dev -p 6006",
@@ -200,7 +200,6 @@ $autocomplete-opacity-visible: 1;
200
200
  .disabled {
201
201
  .control {
202
202
  color: var(--color-background-deselected);
203
- background: var(--color-background-secondary);
204
203
  border-color: var(--color-border-disabled);
205
204
  cursor: not-allowed;
206
205
 
@@ -5,6 +5,27 @@ $button-size-small: 28px;
5
5
  $button-size-default: 32px;
6
6
  $button-size-large: 40px;
7
7
 
8
+ @mixin content-size-tiny {
9
+ @include typography.body-xs-md;
10
+ height: $button-size-tiny;
11
+ border-radius: var(--spacing-4xs);
12
+ --icon-width-tiny: 13px;
13
+ }
14
+
15
+ @mixin content-size-small {
16
+ @include typography.body-xs-md;
17
+ height: $button-size-small;
18
+ }
19
+
20
+ @mixin content-size-medium {
21
+ height: $button-size-default;
22
+ }
23
+
24
+ @mixin content-size-large {
25
+ @include typography.body-md-md;
26
+ height: $button-size-large;
27
+ }
28
+
8
29
  .button {
9
30
  @include typography.body-sm-md;
10
31
  display: flex;
@@ -31,24 +52,19 @@ $button-size-large: 40px;
31
52
  }
32
53
 
33
54
  &.tiny .content {
34
- @include typography.body-xs-md;
35
- height: $button-size-tiny;
36
- border-radius: var(--spacing-4xs);
37
- --icon-width-tiny: 13px;
55
+ @include content-size-tiny;
38
56
  }
39
57
 
40
58
  &.small .content {
41
- @include typography.body-xs-md;
42
- height: $button-size-small;
59
+ @include content-size-small;
43
60
  }
44
61
 
45
62
  &.medium .content {
46
- height: $button-size-default;
63
+ @include content-size-medium;
47
64
  }
48
65
 
49
66
  &.large .content {
50
- @include typography.body-md-md;
51
- height: $button-size-large;
67
+ @include content-size-large;
52
68
  }
53
69
 
54
70
  &.iconButton {
@@ -1,8 +1,9 @@
1
1
  import classNames from 'classnames';
2
2
  import type React from 'react';
3
3
  import { Children, isValidElement } from 'react';
4
+
4
5
  import styles from './ds-button-new.module.scss';
5
- import type { DsButtonProps } from './ds-button-new.types';
6
+ import type { DsButtonBaseProps } from './ds-button-new.types';
6
7
  import { DsIcon } from '../../../ds-icon';
7
8
 
8
9
  const isIconOnly = (children: React.ReactNode) => {
@@ -19,7 +20,7 @@ const isIconOnly = (children: React.ReactNode) => {
19
20
  /**
20
21
  * Design system Button component
21
22
  */
22
- const DsButton: React.FC<DsButtonProps> = ({
23
+ const DsButton: React.FC<DsButtonBaseProps> = ({
23
24
  buttonType,
24
25
  variant = 'filled',
25
26
  size = 'medium',
@@ -36,9 +37,8 @@ const DsButton: React.FC<DsButtonProps> = ({
36
37
  styles[size],
37
38
  className,
38
39
 
39
- // @ts-expect-error: we don't have all variations of classnames defined
40
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
41
- styles[`${type}-${variant}`],
40
+ // Casting because we don't have all variations of classnames defined
41
+ styles[`${type}-${variant}` as keyof typeof styles],
42
42
  );
43
43
 
44
44
  return (
@@ -1,5 +1,7 @@
1
1
  import type React from 'react';
2
2
 
3
+ import type { ResponsiveValue } from '../../../../utils/responsive';
4
+
3
5
  export const buttonTypes = ['primary', 'secondary', 'secondary-light', 'tertiary'] as const;
4
6
  export type ButtonType = (typeof buttonTypes)[number];
5
7
 
@@ -9,7 +11,7 @@ export type ButtonVariant = (typeof buttonVariants)[number];
9
11
  export const buttonSizes = ['large', 'medium', 'small', 'tiny'] as const;
10
12
  export type ButtonSize = (typeof buttonSizes)[number];
11
13
 
12
- export interface DsButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
14
+ export interface DsButtonBaseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
13
15
  /**
14
16
  * Type of the button
15
17
  * @default 'primary'
@@ -39,3 +41,11 @@ export interface DsButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElem
39
41
  */
40
42
  contentClassName?: string;
41
43
  }
44
+
45
+ export interface DsButtonProps extends Omit<DsButtonBaseProps, 'size'> {
46
+ /**
47
+ * Size of the button. Accepts a static value or a responsive object.
48
+ * @default 'medium'
49
+ */
50
+ size?: ResponsiveValue<ButtonSize>;
51
+ }
@@ -1,2 +1,5 @@
1
- export { default as DsButtonNew } from './ds-button-new';
1
+ import { withResponsiveProps } from '../../../../utils/responsive';
2
+ import DsButtonBase from './ds-button-new';
3
+
4
+ export const DsButtonNew = withResponsiveProps(DsButtonBase, ['size']);
2
5
  export * from './ds-button-new.types';
@@ -0,0 +1,391 @@
1
+ @use '../../styles/root_updated';
2
+ @use '../../styles/shared/button' as button;
3
+
4
+ $height-large: 40px;
5
+ $height-medium: 36px;
6
+ $height-small: 28px;
7
+ $border-radius: 4px;
8
+ $focus-ring-width: 2px;
9
+
10
+ @mixin focus-ring($outer-color) {
11
+ outline: $focus-ring-width solid $outer-color;
12
+ }
13
+
14
+ .root {
15
+ box-sizing: border-box;
16
+ display: inline-flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ gap: var(--3xs);
20
+ margin: 0;
21
+ border: 1px solid transparent;
22
+ border-radius: $border-radius;
23
+ background: transparent;
24
+ font-family: var(--font-family-base);
25
+ font-weight: var(--font-weight-medium);
26
+ text-align: center;
27
+ cursor: pointer;
28
+ transition:
29
+ background-color button.$transition-duration-default,
30
+ border-color button.$transition-duration-quick,
31
+ color button.$transition-duration-default,
32
+ outline-color button.$transition-duration-quick;
33
+
34
+ &:disabled:not([data-loading]) {
35
+ cursor: not-allowed;
36
+ }
37
+
38
+ &[data-loading] {
39
+ cursor: default;
40
+ pointer-events: none;
41
+ }
42
+
43
+ &:focus {
44
+ outline: none;
45
+ }
46
+
47
+ &:focus-visible {
48
+ outline-style: solid;
49
+ }
50
+
51
+ &[data-size='large'] {
52
+ padding: var(--3xs) var(--sm);
53
+ min-height: $height-large;
54
+ font-size: var(--font-size-md);
55
+ line-height: var(--line-height-md);
56
+ }
57
+
58
+ &[data-size='medium'] {
59
+ padding: var(--3xs) var(--sm);
60
+ min-height: $height-medium;
61
+ font-size: var(--font-size-sm);
62
+ line-height: var(--line-height-sm);
63
+ }
64
+
65
+ &[data-size='small'] {
66
+ padding: var(--3xs) var(--sm);
67
+ min-height: $height-small;
68
+ font-size: var(--font-size-xs);
69
+ line-height: var(--line-height-xs);
70
+ }
71
+
72
+ &[data-size='tiny'] {
73
+ min-height: var(--icon-width-tiny);
74
+ padding: 0 var(--3xs);
75
+ font-size: var(--font-size-xs);
76
+ line-height: var(--line-height-xs);
77
+ }
78
+
79
+ &[data-icon-only][data-size='large'] {
80
+ padding: var(--3xs);
81
+ min-width: $height-large;
82
+ }
83
+
84
+ &[data-icon-only][data-size='medium'] {
85
+ padding: var(--3xs);
86
+ min-width: $height-medium;
87
+ }
88
+
89
+ &[data-icon-only][data-size='small'] {
90
+ padding: var(--3xs);
91
+ min-width: $height-small;
92
+ }
93
+
94
+ &[data-icon-only][data-size='tiny'] {
95
+ padding: 0;
96
+ min-width: var(--icon-width-tiny);
97
+ }
98
+ }
99
+
100
+ .root[data-color='default'][data-variant='primary'] {
101
+ background-color: var(--background-background-primary);
102
+ color: var(--font-font-on-action);
103
+ border-color: transparent;
104
+
105
+ &:hover:not(:disabled):not(:active):not([data-selected='true']) {
106
+ background-color: var(--background-background-primary-hover);
107
+ }
108
+
109
+ &:active:not(:disabled) {
110
+ background-color: var(--background-background-primary-selected);
111
+ }
112
+
113
+ &:focus-visible {
114
+ @include focus-ring(var(--border-border-action-primary));
115
+
116
+ background-color: var(--background-background-primary-hover);
117
+ border-color: var(--border-border-inverse);
118
+ }
119
+
120
+ &[data-selected='true'] {
121
+ background-color: var(--background-background-primary-selected);
122
+ }
123
+
124
+ &:disabled:not([data-loading]) {
125
+ background-color: var(--background-background-disable);
126
+ color: var(--font-font-on-action);
127
+ }
128
+ }
129
+
130
+ .root[data-color='default'][data-variant='secondary'] {
131
+ background-color: var(--background-background-action-secondary);
132
+ color: var(--font-font-main);
133
+ border-color: var(--border-border);
134
+
135
+ &:hover:not(:disabled):not(:active):not([data-selected='true']) {
136
+ background-color: var(--background-background-secondary-hover);
137
+ color: var(--font-font-action-secondary);
138
+ border-color: var(--border-border-secondary-hover);
139
+ }
140
+
141
+ &:active:not(:disabled) {
142
+ background-color: var(--background-background-action-secondary-hover);
143
+ color: var(--font-font-action-secondary);
144
+ border-color: var(--border-border-secondary-hover);
145
+ }
146
+
147
+ &:focus-visible {
148
+ @include focus-ring(var(--border-border-action-primary-hover));
149
+
150
+ background-color: var(--background-background-secondary-hover);
151
+ border-color: var(--border-border-inverse);
152
+ color: var(--font-font-action-secondary);
153
+ }
154
+
155
+ &[data-selected='true'] {
156
+ background-color: var(--background-background-action-secondary-hover);
157
+ color: var(--font-font-action-secondary);
158
+ border-color: var(--border-border-secondary-hover);
159
+ }
160
+
161
+ &:disabled:not([data-loading]) {
162
+ background-color: var(--background-background-action-secondary);
163
+ color: var(--font-font-disabled);
164
+ border-color: var(--border-border-disabled);
165
+ }
166
+ }
167
+
168
+ .root[data-color='default'][data-variant='tertiary'] {
169
+ background-color: transparent;
170
+ color: var(--font-font-main);
171
+ border-color: transparent;
172
+
173
+ &:hover:not(:disabled):not(:active):not([data-selected='true']) {
174
+ background-color: var(--background-background-secondary-hover);
175
+ color: var(--font-font-action-secondary);
176
+ }
177
+
178
+ &:active:not(:disabled) {
179
+ background-color: var(--background-background-action-secondary-hover);
180
+ color: var(--font-font-action-secondary);
181
+ }
182
+
183
+ &:focus-visible {
184
+ @include focus-ring(var(--border-border-action-primary-hover));
185
+
186
+ background-color: var(--background-background-secondary-hover);
187
+ border-color: var(--border-border-inverse);
188
+ color: var(--font-font-action-secondary);
189
+ }
190
+
191
+ &[data-selected='true'] {
192
+ background-color: var(--background-background-action-secondary-hover);
193
+ color: var(--font-font-action-secondary);
194
+ }
195
+
196
+ &:disabled:not([data-loading]) {
197
+ background-color: transparent;
198
+ color: var(--font-font-disabled);
199
+ }
200
+ }
201
+
202
+ .root[data-color='negative'][data-variant='primary'] {
203
+ background-color: var(--background-background-negative);
204
+ color: var(--font-font-on-action);
205
+ border-color: transparent;
206
+
207
+ &:hover:not(:disabled):not(:active):not([data-selected='true']) {
208
+ background-color: var(--background-background-negative-hover);
209
+ }
210
+
211
+ &:active:not(:disabled) {
212
+ background-color: var(--background-background-negative-selected);
213
+ }
214
+
215
+ &:focus-visible {
216
+ @include focus-ring(var(--border-border-negative));
217
+
218
+ background-color: var(--background-background-negative-hover);
219
+ border-color: var(--border-border-inverse);
220
+ }
221
+
222
+ &[data-selected='true'] {
223
+ background-color: var(--background-background-negative-selected);
224
+ }
225
+
226
+ &:disabled:not([data-loading]) {
227
+ background-color: var(--background-background-disable);
228
+ color: var(--font-font-on-action);
229
+ }
230
+ }
231
+
232
+ .root[data-color='negative'][data-variant='secondary'] {
233
+ background-color: var(--background-background-action-secondary);
234
+ color: var(--font-font-negative);
235
+ border-color: var(--border-border-negative);
236
+
237
+ &:hover:not(:disabled):not(:active):not([data-selected='true']) {
238
+ background-color: var(--background-background-negative-secondary-hover);
239
+ border-color: var(--border-border-negative-hover);
240
+ }
241
+
242
+ &:active:not(:disabled) {
243
+ background-color: var(--background-background-negative-secondary-selected);
244
+ border-color: var(--border-border-negative-hover);
245
+ }
246
+
247
+ &:focus-visible {
248
+ @include focus-ring(var(--border-border-negative));
249
+
250
+ background-color: var(--background-background-negative-secondary-hover);
251
+ border-color: var(--border-border-inverse);
252
+ }
253
+
254
+ &[data-selected='true'] {
255
+ background-color: var(--background-background-negative-secondary-selected);
256
+ border-color: var(--border-border-negative-hover);
257
+ }
258
+
259
+ &:disabled:not([data-loading]) {
260
+ background-color: var(--background-background-action-secondary);
261
+ color: var(--font-font-disabled);
262
+ border-color: var(--border-border-disabled);
263
+ }
264
+ }
265
+
266
+ .root[data-color='negative'][data-variant='tertiary'] {
267
+ background-color: transparent;
268
+ color: var(--font-font-negative);
269
+ border-color: transparent;
270
+
271
+ &:hover:not(:disabled):not(:active):not([data-selected='true']) {
272
+ background-color: var(--background-background-negative-secondary-hover);
273
+ }
274
+
275
+ &:active:not(:disabled) {
276
+ background-color: var(--background-background-negative-secondary-selected);
277
+ }
278
+
279
+ &:focus-visible {
280
+ @include focus-ring(var(--border-border-negative));
281
+
282
+ background-color: var(--background-background-negative-secondary-hover);
283
+ border-color: var(--border-border-inverse);
284
+ }
285
+
286
+ &[data-selected='true'] {
287
+ background-color: var(--background-background-negative-secondary-selected);
288
+ }
289
+
290
+ &:disabled:not([data-loading]) {
291
+ background-color: transparent;
292
+ color: var(--font-font-disabled);
293
+ }
294
+ }
295
+
296
+ .root[data-color='light'][data-variant='primary'] {
297
+ background-color: var(--background-background-ondark-primary);
298
+ color: var(--font-font-on-action);
299
+ border-color: transparent;
300
+
301
+ &:hover:not(:disabled):not(:active):not([data-selected='true']) {
302
+ background-color: var(--background-background-ondark-primary-hover);
303
+ }
304
+
305
+ &:active:not(:disabled) {
306
+ background-color: var(--background-background-ondark-primary-selected);
307
+ }
308
+
309
+ &:focus-visible {
310
+ @include focus-ring(var(--outline-outline-inverse));
311
+
312
+ background-color: var(--background-background-ondark-primary-hover);
313
+ border-color: var(--border-border-action-secondary);
314
+ }
315
+
316
+ &[data-selected='true'] {
317
+ background-color: var(--background-background-ondark-primary-selected);
318
+ }
319
+
320
+ &:disabled:not([data-loading]) {
321
+ background-color: var(--background-background-ondark-disabled);
322
+ color: var(--font-font-disabled);
323
+ }
324
+ }
325
+
326
+ .root[data-color='light'][data-variant='secondary'] {
327
+ background-color: transparent;
328
+ color: var(--font-font-on-action);
329
+ border-color: var(--border-border-ondark-secondary);
330
+
331
+ &:hover:not(:disabled):not(:active):not([data-selected='true']) {
332
+ background-color: var(--background-background-ondark-secondary-hover);
333
+ color: var(--font-font-on-action);
334
+ }
335
+
336
+ &:active:not(:disabled) {
337
+ background-color: var(--background-background-ondark-secondary-selected);
338
+ color: var(--font-font-on-action);
339
+ }
340
+
341
+ &:focus-visible {
342
+ @include focus-ring(var(--outline-outline-inverse));
343
+
344
+ background-color: var(--background-background-ondark-secondary-hover);
345
+ border-color: var(--border-border-action-secondary);
346
+ color: var(--font-font-on-action);
347
+ }
348
+
349
+ &[data-selected='true'] {
350
+ background-color: var(--background-background-ondark-secondary-selected);
351
+ }
352
+
353
+ &:disabled:not([data-loading]) {
354
+ background-color: transparent;
355
+ color: var(--font-font-ondark-disabled);
356
+ border-color: var(--background-background-ondark-disabled);
357
+ }
358
+ }
359
+
360
+ .root[data-color='light'][data-variant='tertiary'] {
361
+ background-color: transparent;
362
+ color: var(--font-font-on-action);
363
+ border-color: transparent;
364
+
365
+ &:hover:not(:disabled):not(:active):not([data-selected='true']) {
366
+ background-color: var(--background-background-ondark-secondary-hover);
367
+ color: var(--font-font-on-action);
368
+ }
369
+
370
+ &:active:not(:disabled) {
371
+ background-color: var(--background-background-ondark-secondary-selected);
372
+ color: var(--font-font-on-action);
373
+ }
374
+
375
+ &:focus-visible {
376
+ @include focus-ring(var(--outline-outline-inverse));
377
+
378
+ background-color: var(--background-background-ondark-secondary-hover);
379
+ border-color: var(--border-border-action-secondary);
380
+ color: var(--font-font-on-action);
381
+ }
382
+
383
+ &[data-selected='true'] {
384
+ background-color: var(--background-background-ondark-secondary-selected);
385
+ }
386
+
387
+ &:disabled:not([data-loading]) {
388
+ background-color: transparent;
389
+ color: var(--font-font-ondark-disabled);
390
+ }
391
+ }
@@ -0,0 +1,55 @@
1
+ import classNames from 'classnames';
2
+ import { DsIcon, type IconSize } from '../ds-icon';
3
+ import { DsSpinner } from '../ds-spinner';
4
+ import styles from './ds-button-v3.module.scss';
5
+ import type { ButtonV3Size, DsButtonV3Props } from './ds-button-v3.types.ts';
6
+
7
+ const iconSizeMap: Record<ButtonV3Size, IconSize> = Object.freeze({
8
+ large: 'small',
9
+ medium: 'tiny',
10
+ small: 'tiny',
11
+ tiny: 'tiny',
12
+ });
13
+
14
+ const DsButtonV3 = ({
15
+ ref,
16
+ className,
17
+ style,
18
+ children,
19
+ icon,
20
+ disabled,
21
+ loading = false,
22
+ color = 'default',
23
+ variant = 'primary',
24
+ size = 'medium',
25
+ selected = false,
26
+ type = 'button',
27
+ ...rest
28
+ }: DsButtonV3Props) => {
29
+ const isIconOnly = icon !== undefined && !children;
30
+
31
+ return (
32
+ <button
33
+ ref={ref}
34
+ // eslint-disable-next-line react/button-has-type -- Dynamic by nature of this component
35
+ type={type}
36
+ disabled={disabled || loading}
37
+ aria-busy={loading || undefined}
38
+ aria-pressed={selected}
39
+ className={classNames(styles.root, className)}
40
+ style={style}
41
+ data-color={color}
42
+ data-variant={variant}
43
+ data-size={size}
44
+ data-selected={selected ? 'true' : undefined}
45
+ data-icon-only={isIconOnly || undefined}
46
+ data-loading={loading && !disabled ? '' : undefined}
47
+ {...rest}
48
+ >
49
+ {loading ? <DsSpinner /> : icon && <DsIcon icon={icon} size={iconSizeMap[size]} aria-hidden />}
50
+ {children}
51
+ </button>
52
+ );
53
+ };
54
+
55
+ export default DsButtonV3;
@@ -0,0 +1,49 @@
1
+ import type { ButtonHTMLAttributes, Ref } from 'react';
2
+ import type { IconType } from '../ds-icon';
3
+
4
+ export const buttonV3Variants = ['primary', 'secondary', 'tertiary'] as const;
5
+ export type ButtonV3Variant = (typeof buttonV3Variants)[number];
6
+
7
+ export const buttonV3Colors = ['default', 'negative', 'light'] as const;
8
+ export type ButtonV3Color = (typeof buttonV3Colors)[number];
9
+
10
+ export const buttonV3Sizes = ['large', 'medium', 'small', 'tiny'] as const;
11
+ export type ButtonV3Size = (typeof buttonV3Sizes)[number];
12
+
13
+ export interface DsButtonV3Props extends ButtonHTMLAttributes<HTMLButtonElement> {
14
+ ref?: Ref<HTMLButtonElement>;
15
+
16
+ /**
17
+ * - `default` — standard light-UI palette
18
+ * - `negative` — destructive / danger palette (red tones)
19
+ * - `light` — palette for dark-background surfaces (Figma **Type** onDark)
20
+ * @default 'default'
21
+ */
22
+ color?: ButtonV3Color;
23
+
24
+ /**
25
+ * @default 'primary'
26
+ */
27
+ variant?: ButtonV3Variant;
28
+
29
+ /**
30
+ * @default 'medium'
31
+ */
32
+ size?: ButtonV3Size;
33
+
34
+ /**
35
+ * @default false
36
+ */
37
+ selected?: boolean;
38
+
39
+ /**
40
+ * Leading icon. When set without children, renders as icon-only (square) layout.
41
+ */
42
+ icon?: IconType;
43
+
44
+ /**
45
+ * Shows a spinner as the leading element and disables interaction.
46
+ * @default false
47
+ */
48
+ loading?: boolean;
49
+ }