@donkit-ai/design-system 0.2.13 → 0.2.15

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.
package/README.md CHANGED
@@ -37,8 +37,8 @@ npm install @donkit-ai/design-system@latest
37
37
 
38
38
  ## Компоненты
39
39
 
40
- - **Button** - кнопки с вариантами (primary, secondary, ghost) и размерами (small, medium, large)
41
- - **Tabs** - вкладки с состоянием selected в трех размерах (small, medium, large)
40
+ - **Button** - кнопки с вариантами (primary, secondary, ghost) и размерами (small, medium, large). Поддерживает `href` для рендера как ссылка
41
+ - **Tabs** - вкладки с состоянием selected в трех размерах (small, medium, large). Поддерживает `href` для рендера как ссылка
42
42
  - **Input** - текстовые поля с поддержкой иконок, ошибок и подсказок (small, medium)
43
43
  - **Stepper** - числовое поле с кнопками +/- для изменения значения (small, medium)
44
44
  - **Card** - карточки двух типов: info (информационная, прозрачный фон) и interactive (интерактивная с hover эффектом)
@@ -81,6 +81,17 @@ function MyComponent() {
81
81
  </Card>
82
82
  );
83
83
  }
84
+
85
+ // Button как ссылка (рендерит <a> вместо <button>)
86
+ // При обычном клике: вызывается onClick (preventDefault автоматический)
87
+ // При Cmd/Ctrl+Click или Middle Click: открывается новая вкладка
88
+ <Button variant="primary" href="/dashboard" onClick={() => navigate('/dashboard')}>
89
+ Go to Dashboard
90
+ </Button>
91
+
92
+ <Button variant="secondary" href="https://example.com">
93
+ Visit Example
94
+ </Button>
84
95
  ```
85
96
 
86
97
  ### 3. Иконки
@@ -94,7 +105,7 @@ function MyComponent() {
94
105
  ```javascript
95
106
  import { iconSizes } from '@donkit-ai/design-system';
96
107
 
97
- // iconSizes = { xs: 16, s: 20, m: 24, l: 28, xl: 32 }
108
+ // iconSizes = { xs: 16, s: 20, m: 24, l: 28, xl: 48 }
98
109
  ```
99
110
 
100
111
  **Соответствие размеров компонентам:**
@@ -115,7 +126,7 @@ import { iconSizes } from '@donkit-ai/design-system';
115
126
  - **28px (l)** - крупные элементы
116
127
  - Large кнопки (`size="large"`)
117
128
 
118
- - **32px (xl)** - очень крупные элементы
129
+ - **48px (xl)** - очень крупные элементы
119
130
 
120
131
  **Всегда используется `strokeWidth={1.5}`** для единообразия дизайна.
121
132
 
@@ -382,6 +393,21 @@ import { AlertCircle } from 'lucide-react';
382
393
  <Tab selected>Active</Tab>
383
394
  <Tab disabled>Disabled</Tab>
384
395
  </Tabs>
396
+
397
+ // As links (с href) - рендерит <a> вместо <button>
398
+ // При обычном клике: вызывается onClick (preventDefault автоматический)
399
+ // При Cmd/Ctrl+Click или Middle Click: открывается новая вкладка
400
+ <Tabs size="medium">
401
+ <Tab selected href="/overview" onClick={() => navigate('/overview')}>
402
+ Overview
403
+ </Tab>
404
+ <Tab href="/details" onClick={() => navigate('/details')}>
405
+ Details
406
+ </Tab>
407
+ <Tab href="/settings" onClick={() => navigate('/settings')}>
408
+ Settings
409
+ </Tab>
410
+ </Tabs>
385
411
  ```
386
412
 
387
413
  **Стили:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkit-ai/design-system",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
4
4
  "description": "Donkit Design System - minimal design tokens and React components",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -13,11 +13,14 @@
13
13
  border-color var(--transition-normal),
14
14
  opacity var(--transition-normal);
15
15
  white-space: nowrap;
16
+ text-decoration: none;
16
17
  }
17
18
 
18
- .ds-button:disabled {
19
+ .ds-button:disabled,
20
+ .ds-button[aria-disabled="true"] {
19
21
  opacity: 0.5;
20
22
  cursor: not-allowed;
23
+ pointer-events: none;
21
24
  }
22
25
 
23
26
  .ds-button__icon {
@@ -32,7 +35,7 @@
32
35
  background-color: var(--color-accent);
33
36
  }
34
37
 
35
- .ds-button--primary:hover:not(:disabled) {
38
+ .ds-button--primary:hover:not(:disabled):not([aria-disabled="true"]) {
36
39
  background-color: var(--color-accent-hover);
37
40
  }
38
41
 
@@ -42,7 +45,7 @@
42
45
  border: 1px solid var(--color-border);
43
46
  }
44
47
 
45
- .ds-button--secondary:hover:not(:disabled) {
48
+ .ds-button--secondary:hover:not(:disabled):not([aria-disabled="true"]) {
46
49
  background-color: var(--color-item-bg-hover);
47
50
  border-color: var(--color-border-hover);
48
51
  }
@@ -52,7 +55,7 @@
52
55
  background-color: transparent;
53
56
  }
54
57
 
55
- .ds-button--ghost:hover:not(:disabled) {
58
+ .ds-button--ghost:hover:not(:disabled):not([aria-disabled="true"]) {
56
59
  background-color: var(--color-item-bg-hover);
57
60
  color: var(--color-txt-icon-1);
58
61
  }
@@ -10,6 +10,7 @@ export function Button({
10
10
  disabled = false,
11
11
  onClick,
12
12
  type = 'button',
13
+ href,
13
14
  'aria-label': ariaLabel,
14
15
  ...props
15
16
  }) {
@@ -23,6 +24,47 @@ export function Button({
23
24
  isIconOnly && 'ds-button--icon-only',
24
25
  ].filter(Boolean).join(' ');
25
26
 
27
+ const content = (
28
+ <>
29
+ {icon && !isIconOnly && <span className="ds-button__icon" aria-hidden="true">{icon}</span>}
30
+ {children}
31
+ {isIconOnly && <span className="ds-button__icon" aria-hidden="true">{icon}</span>}
32
+ </>
33
+ );
34
+
35
+ // Render as link if href is provided
36
+ if (href) {
37
+ const handleClick = (e) => {
38
+ if (disabled) {
39
+ e.preventDefault();
40
+ return;
41
+ }
42
+
43
+ // При Cmd/Ctrl+Click или Middle Click — пусть браузер откроет новую вкладку
44
+ if (e.metaKey || e.ctrlKey || e.button === 1) {
45
+ return;
46
+ }
47
+
48
+ // Обычный клик — preventDefault и вызов onClick
49
+ e.preventDefault();
50
+ onClick?.(e);
51
+ };
52
+
53
+ return (
54
+ <a
55
+ className={className}
56
+ href={disabled ? undefined : href}
57
+ onClick={handleClick}
58
+ aria-label={ariaLabel}
59
+ aria-disabled={disabled ? 'true' : undefined}
60
+ {...props}
61
+ >
62
+ {content}
63
+ </a>
64
+ );
65
+ }
66
+
67
+ // Render as button (default)
26
68
  return (
27
69
  <button
28
70
  type={type}
@@ -32,9 +74,7 @@ export function Button({
32
74
  aria-label={ariaLabel}
33
75
  {...props}
34
76
  >
35
- {icon && !isIconOnly && <span className="ds-button__icon" aria-hidden="true">{icon}</span>}
36
- {children}
37
- {isIconOnly && <span className="ds-button__icon" aria-hidden="true">{icon}</span>}
77
+ {content}
38
78
  </button>
39
79
  );
40
80
  }
@@ -8,10 +8,10 @@
8
8
  .ds-card--interactive {
9
9
  cursor: pointer;
10
10
  background-color: var(--color-item-bg);
11
+ border: none;
11
12
  }
12
13
 
13
14
  .ds-card--interactive:hover {
14
- border-color: var(--color-border-hover);
15
15
  background-color: var(--color-item-bg-hover);
16
16
  }
17
17
 
@@ -17,6 +17,7 @@
17
17
  color: var(--color-txt-icon-1);
18
18
  transition: border-color var(--transition-normal), background-color var(--transition-normal);
19
19
  white-space: nowrap;
20
+ text-decoration: none;
20
21
  }
21
22
 
22
23
  /* Variants */
@@ -25,7 +26,7 @@
25
26
  color: var(--color-txt-icon-2);
26
27
  }
27
28
 
28
- .ds-tab--ghost:hover:not(.ds-tab--selected):not(:disabled) {
29
+ .ds-tab--ghost:hover:not(.ds-tab--selected):not(:disabled):not([aria-disabled="true"]) {
29
30
  background-color: var(--color-item-bg-hover);
30
31
  color: var(--color-txt-icon-1);
31
32
  }
@@ -58,9 +59,11 @@
58
59
  gap: var(--space-s);
59
60
  }
60
61
 
61
- .ds-tab:disabled {
62
+ .ds-tab:disabled,
63
+ .ds-tab[aria-disabled="true"] {
62
64
  opacity: 0.5;
63
65
  cursor: not-allowed;
66
+ pointer-events: none;
64
67
  }
65
68
 
66
69
  .ds-tab-icon {
@@ -14,7 +14,7 @@ export function Tabs({ children, size = 'medium', variant = 'ghost', ...props })
14
14
  );
15
15
  }
16
16
 
17
- export function Tab({ children, selected = false, onClick, size = 'medium', variant = 'ghost', disabled = false, icon, ...props }) {
17
+ export function Tab({ children, selected = false, onClick, size = 'medium', variant = 'ghost', disabled = false, icon, href, ...props }) {
18
18
  const isIconOnly = icon && !children;
19
19
 
20
20
  const className = [
@@ -25,6 +25,47 @@ export function Tab({ children, selected = false, onClick, size = 'medium', vari
25
25
  isIconOnly && 'ds-tab--icon-only',
26
26
  ].filter(Boolean).join(' ');
27
27
 
28
+ const content = (
29
+ <>
30
+ {icon && <span className="ds-tab-icon">{icon}</span>}
31
+ {children}
32
+ </>
33
+ );
34
+
35
+ // Render as link if href is provided
36
+ if (href) {
37
+ const handleClick = (e) => {
38
+ if (disabled) {
39
+ e.preventDefault();
40
+ return;
41
+ }
42
+
43
+ // При Cmd/Ctrl+Click или Middle Click — пусть браузер откроет новую вкладку
44
+ if (e.metaKey || e.ctrlKey || e.button === 1) {
45
+ return;
46
+ }
47
+
48
+ // Обычный клик — preventDefault и вызов onClick
49
+ e.preventDefault();
50
+ onClick?.(e);
51
+ };
52
+
53
+ return (
54
+ <a
55
+ role="tab"
56
+ aria-current={selected ? 'page' : undefined}
57
+ aria-disabled={disabled ? 'true' : undefined}
58
+ className={className}
59
+ href={disabled ? undefined : href}
60
+ onClick={handleClick}
61
+ {...props}
62
+ >
63
+ {content}
64
+ </a>
65
+ );
66
+ }
67
+
68
+ // Render as button (default)
28
69
  return (
29
70
  <button
30
71
  role="tab"
@@ -34,8 +75,7 @@ export function Tab({ children, selected = false, onClick, size = 'medium', vari
34
75
  disabled={disabled}
35
76
  {...props}
36
77
  >
37
- {icon && <span className="ds-tab-icon">{icon}</span>}
38
- {children}
78
+ {content}
39
79
  </button>
40
80
  );
41
81
  }
@@ -4,12 +4,12 @@
4
4
  * --icon-s: 20px
5
5
  * --icon-m: 24px
6
6
  * --icon-l: 28px
7
- * --icon-xl: 32px
7
+ * --icon-xl: 48px
8
8
  */
9
9
  export const iconSizes = {
10
10
  xs: 16,
11
11
  s: 20,
12
12
  m: 24,
13
13
  l: 28,
14
- xl: 32,
14
+ xl: 48,
15
15
  };
@@ -180,7 +180,7 @@
180
180
  --icon-s: 20px;
181
181
  --icon-m: 24px;
182
182
  --icon-l: 28px;
183
- --icon-xl: 32px;
183
+ --icon-xl: 48px;
184
184
  }
185
185
 
186
186
  :root {