@donkit-ai/design-system 0.2.14 → 0.2.16
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 +83 -6
- package/package.json +1 -1
- package/src/components/Button.css +7 -4
- package/src/components/Button.jsx +43 -3
- package/src/components/Tabs.jsx +17 -1
- package/src/components/Toggle.css +91 -0
- package/src/components/Toggle.jsx +38 -0
- package/src/index.js +1 -0
package/README.md
CHANGED
|
@@ -37,10 +37,11 @@ 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
|
+
- **Toggle** - переключатель для включения/выключения опций (small, medium)
|
|
44
45
|
- **Card** - карточки двух типов: info (информационная, прозрачный фон) и interactive (интерактивная с hover эффектом)
|
|
45
46
|
- **Typography** - H1-H4, P1-P3 компоненты
|
|
46
47
|
- **Code** - inline и block код с monospace шрифтом
|
|
@@ -81,6 +82,17 @@ function MyComponent() {
|
|
|
81
82
|
</Card>
|
|
82
83
|
);
|
|
83
84
|
}
|
|
85
|
+
|
|
86
|
+
// Button как ссылка (рендерит <a> вместо <button>)
|
|
87
|
+
// При обычном клике: вызывается onClick (preventDefault автоматический)
|
|
88
|
+
// При Cmd/Ctrl+Click или Middle Click: открывается новая вкладка
|
|
89
|
+
<Button variant="primary" href="/dashboard" onClick={() => navigate('/dashboard')}>
|
|
90
|
+
Go to Dashboard
|
|
91
|
+
</Button>
|
|
92
|
+
|
|
93
|
+
<Button variant="secondary" href="https://example.com">
|
|
94
|
+
Visit Example
|
|
95
|
+
</Button>
|
|
84
96
|
```
|
|
85
97
|
|
|
86
98
|
### 3. Иконки
|
|
@@ -384,15 +396,16 @@ import { AlertCircle } from 'lucide-react';
|
|
|
384
396
|
</Tabs>
|
|
385
397
|
|
|
386
398
|
// As links (с href) - рендерит <a> вместо <button>
|
|
387
|
-
//
|
|
399
|
+
// При обычном клике: вызывается onClick (preventDefault автоматический)
|
|
400
|
+
// При Cmd/Ctrl+Click или Middle Click: открывается новая вкладка
|
|
388
401
|
<Tabs size="medium">
|
|
389
|
-
<Tab selected href="/overview">
|
|
402
|
+
<Tab selected href="/overview" onClick={() => navigate('/overview')}>
|
|
390
403
|
Overview
|
|
391
404
|
</Tab>
|
|
392
|
-
<Tab href="/details">
|
|
405
|
+
<Tab href="/details" onClick={() => navigate('/details')}>
|
|
393
406
|
Details
|
|
394
407
|
</Tab>
|
|
395
|
-
<Tab href="/settings">
|
|
408
|
+
<Tab href="/settings" onClick={() => navigate('/settings')}>
|
|
396
409
|
Settings
|
|
397
410
|
</Tab>
|
|
398
411
|
</Tabs>
|
|
@@ -535,6 +548,70 @@ import { AlertCircle } from 'lucide-react';
|
|
|
535
548
|
- Small: 16px (Minus, Plus)
|
|
536
549
|
- Medium: 20px (Minus, Plus)
|
|
537
550
|
|
|
551
|
+
### Toggle
|
|
552
|
+
|
|
553
|
+
Переключатель для включения/выключения опций. Высота совпадает с другими элементами интерфейса.
|
|
554
|
+
|
|
555
|
+
```jsx
|
|
556
|
+
// Basic toggle
|
|
557
|
+
<Toggle
|
|
558
|
+
checked={isEnabled}
|
|
559
|
+
onChange={setIsEnabled}
|
|
560
|
+
label="Enable notifications"
|
|
561
|
+
/>
|
|
562
|
+
|
|
563
|
+
// Small size
|
|
564
|
+
<Toggle
|
|
565
|
+
size="small"
|
|
566
|
+
checked={isEnabled}
|
|
567
|
+
onChange={setIsEnabled}
|
|
568
|
+
label="Auto-save"
|
|
569
|
+
/>
|
|
570
|
+
|
|
571
|
+
// Medium size (default)
|
|
572
|
+
<Toggle
|
|
573
|
+
size="medium"
|
|
574
|
+
checked={darkMode}
|
|
575
|
+
onChange={setDarkMode}
|
|
576
|
+
label="Dark mode"
|
|
577
|
+
/>
|
|
578
|
+
|
|
579
|
+
// Without label (for tables/cards where context is clear)
|
|
580
|
+
<Toggle
|
|
581
|
+
checked={isEnabled}
|
|
582
|
+
onChange={setIsEnabled}
|
|
583
|
+
/>
|
|
584
|
+
|
|
585
|
+
// Disabled states
|
|
586
|
+
<Toggle
|
|
587
|
+
checked={false}
|
|
588
|
+
onChange={() => {}}
|
|
589
|
+
label="Disabled unchecked"
|
|
590
|
+
disabled
|
|
591
|
+
/>
|
|
592
|
+
|
|
593
|
+
<Toggle
|
|
594
|
+
checked={true}
|
|
595
|
+
onChange={() => {}}
|
|
596
|
+
label="Disabled checked"
|
|
597
|
+
disabled
|
|
598
|
+
/>
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
**Параметры:**
|
|
602
|
+
- `checked` - состояние переключателя (true/false)
|
|
603
|
+
- `onChange` - функция обработки изменения состояния, получает новое значение (boolean)
|
|
604
|
+
- `size` - размер: "small" или "medium" (default: "medium")
|
|
605
|
+
- `label` - текст подписи (опционально)
|
|
606
|
+
- `disabled` - отключить переключатель
|
|
607
|
+
- `id` - пользовательский ID (по умолчанию генерируется автоматически)
|
|
608
|
+
|
|
609
|
+
**Стиль:**
|
|
610
|
+
- Высота совпадает с Input, Select, Stepper (`--height-s` / `--height-m`)
|
|
611
|
+
- Ширина трека: 1.75x от высоты
|
|
612
|
+
- Включенное состояние: фон `--color-status-success`, border `--color-status-success` (зеленый)
|
|
613
|
+
- Выключенное состояние: фон `--color-item-bg`, border `--color-border`, hover `--color-border-hover`
|
|
614
|
+
|
|
538
615
|
### Card
|
|
539
616
|
|
|
540
617
|
Карточки бывают двух типов:
|
package/package.json
CHANGED
|
@@ -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
|
-
{
|
|
36
|
-
{children}
|
|
37
|
-
{isIconOnly && <span className="ds-button__icon" aria-hidden="true">{icon}</span>}
|
|
77
|
+
{content}
|
|
38
78
|
</button>
|
|
39
79
|
);
|
|
40
80
|
}
|
package/src/components/Tabs.jsx
CHANGED
|
@@ -34,6 +34,22 @@ export function Tab({ children, selected = false, onClick, size = 'medium', vari
|
|
|
34
34
|
|
|
35
35
|
// Render as link if href is provided
|
|
36
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
|
+
|
|
37
53
|
return (
|
|
38
54
|
<a
|
|
39
55
|
role="tab"
|
|
@@ -41,7 +57,7 @@ export function Tab({ children, selected = false, onClick, size = 'medium', vari
|
|
|
41
57
|
aria-disabled={disabled ? 'true' : undefined}
|
|
42
58
|
className={className}
|
|
43
59
|
href={disabled ? undefined : href}
|
|
44
|
-
onClick={
|
|
60
|
+
onClick={handleClick}
|
|
45
61
|
{...props}
|
|
46
62
|
>
|
|
47
63
|
{content}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
.ds-toggle {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: var(--space-s);
|
|
5
|
+
cursor: pointer;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.ds-toggle--disabled {
|
|
9
|
+
opacity: 0.5;
|
|
10
|
+
cursor: not-allowed;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ds-toggle__input {
|
|
14
|
+
position: absolute;
|
|
15
|
+
opacity: 0;
|
|
16
|
+
pointer-events: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ds-toggle__track {
|
|
20
|
+
position: relative;
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
background-color: var(--color-item-bg);
|
|
24
|
+
border: 1px solid var(--color-border);
|
|
25
|
+
transition: background-color var(--transition-normal), border-color var(--transition-normal);
|
|
26
|
+
flex-shrink: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ds-toggle__input:checked + .ds-toggle__track {
|
|
30
|
+
background-color: var(--color-status-success);
|
|
31
|
+
border-color: var(--color-status-success);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.ds-toggle__input:not(:checked) + .ds-toggle__track:hover {
|
|
35
|
+
border-color: var(--color-border-hover);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.ds-toggle__thumb {
|
|
39
|
+
background-color: var(--color-white);
|
|
40
|
+
border-radius: 50%;
|
|
41
|
+
transition: transform var(--transition-normal);
|
|
42
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.ds-toggle__label {
|
|
46
|
+
font-size: var(--font-size-p1);
|
|
47
|
+
color: var(--color-txt-icon-1);
|
|
48
|
+
user-select: none;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Small */
|
|
52
|
+
.ds-toggle--small .ds-toggle__track {
|
|
53
|
+
width: calc(var(--height-s) * 1.75);
|
|
54
|
+
height: var(--height-s);
|
|
55
|
+
border-radius: calc(var(--height-s) / 2);
|
|
56
|
+
padding: 3px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.ds-toggle--small .ds-toggle__thumb {
|
|
60
|
+
width: calc(var(--height-s) - 8px);
|
|
61
|
+
height: calc(var(--height-s) - 8px);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.ds-toggle--small .ds-toggle__input:checked + .ds-toggle__track .ds-toggle__thumb {
|
|
65
|
+
transform: translateX(calc(var(--height-s) * 0.75));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ds-toggle--small .ds-toggle__label {
|
|
69
|
+
font-size: var(--font-size-p2);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Medium */
|
|
73
|
+
.ds-toggle--medium .ds-toggle__track {
|
|
74
|
+
width: calc(var(--height-m) * 1.75);
|
|
75
|
+
height: var(--height-m);
|
|
76
|
+
border-radius: calc(var(--height-m) / 2);
|
|
77
|
+
padding: 4px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.ds-toggle--medium .ds-toggle__thumb {
|
|
81
|
+
width: calc(var(--height-m) - 10px);
|
|
82
|
+
height: calc(var(--height-m) - 10px);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.ds-toggle--medium .ds-toggle__input:checked + .ds-toggle__track .ds-toggle__thumb {
|
|
86
|
+
transform: translateX(calc(var(--height-m) * 0.75));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.ds-toggle--medium .ds-toggle__label {
|
|
90
|
+
font-size: var(--font-size-p1);
|
|
91
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './Toggle.css';
|
|
3
|
+
|
|
4
|
+
export function Toggle({
|
|
5
|
+
checked = false,
|
|
6
|
+
onChange,
|
|
7
|
+
size = 'medium',
|
|
8
|
+
disabled = false,
|
|
9
|
+
label,
|
|
10
|
+
id,
|
|
11
|
+
...props
|
|
12
|
+
}) {
|
|
13
|
+
const toggleId = id || `toggle-${React.useId()}`;
|
|
14
|
+
|
|
15
|
+
const className = [
|
|
16
|
+
'ds-toggle',
|
|
17
|
+
`ds-toggle--${size}`,
|
|
18
|
+
disabled && 'ds-toggle--disabled',
|
|
19
|
+
].filter(Boolean).join(' ');
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<label className={className} htmlFor={toggleId}>
|
|
23
|
+
<input
|
|
24
|
+
type="checkbox"
|
|
25
|
+
id={toggleId}
|
|
26
|
+
className="ds-toggle__input"
|
|
27
|
+
checked={checked}
|
|
28
|
+
onChange={(e) => onChange?.(e.target.checked)}
|
|
29
|
+
disabled={disabled}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
<span className="ds-toggle__track">
|
|
33
|
+
<span className="ds-toggle__thumb" />
|
|
34
|
+
</span>
|
|
35
|
+
{label && <span className="ds-toggle__label">{label}</span>}
|
|
36
|
+
</label>
|
|
37
|
+
);
|
|
38
|
+
}
|
package/src/index.js
CHANGED