@donkit-ai/design-system 0.2.12 → 0.2.14
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 +20 -1
- package/package.json +1 -1
- package/src/components/Card.css +1 -1
- package/src/components/Tabs.css +5 -2
- package/src/components/Tabs.jsx +27 -3
- package/src/components/Tooltip.css +6 -6
- package/src/components/Tooltip.jsx +35 -0
- package/src/styles/iconSizes.js +2 -0
- package/src/styles/tokens.css +1 -0
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ function MyComponent() {
|
|
|
94
94
|
```javascript
|
|
95
95
|
import { iconSizes } from '@donkit-ai/design-system';
|
|
96
96
|
|
|
97
|
-
// iconSizes = { xs: 16, s: 20, m: 24, l: 28 }
|
|
97
|
+
// iconSizes = { xs: 16, s: 20, m: 24, l: 28, xl: 48 }
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
**Соответствие размеров компонентам:**
|
|
@@ -115,6 +115,8 @@ import { iconSizes } from '@donkit-ai/design-system';
|
|
|
115
115
|
- **28px (l)** - крупные элементы
|
|
116
116
|
- Large кнопки (`size="large"`)
|
|
117
117
|
|
|
118
|
+
- **48px (xl)** - очень крупные элементы
|
|
119
|
+
|
|
118
120
|
**Всегда используется `strokeWidth={1.5}`** для единообразия дизайна.
|
|
119
121
|
|
|
120
122
|
#### Примеры использования
|
|
@@ -148,6 +150,9 @@ import { iconSizes } from '@donkit-ai/design-system';
|
|
|
148
150
|
<Button size="large" icon={<Mail size={iconSizes.l} strokeWidth={1.5} />}>
|
|
149
151
|
Send Email
|
|
150
152
|
</Button>
|
|
153
|
+
|
|
154
|
+
// Extra Large icons
|
|
155
|
+
<Mail size={iconSizes.xl} strokeWidth={1.5} />
|
|
151
156
|
```
|
|
152
157
|
|
|
153
158
|
### 3. Переключение темы
|
|
@@ -377,6 +382,20 @@ import { AlertCircle } from 'lucide-react';
|
|
|
377
382
|
<Tab selected>Active</Tab>
|
|
378
383
|
<Tab disabled>Disabled</Tab>
|
|
379
384
|
</Tabs>
|
|
385
|
+
|
|
386
|
+
// As links (с href) - рендерит <a> вместо <button>
|
|
387
|
+
// Поддерживает открытие в новой вкладке и работает с роутингом
|
|
388
|
+
<Tabs size="medium">
|
|
389
|
+
<Tab selected href="/overview">
|
|
390
|
+
Overview
|
|
391
|
+
</Tab>
|
|
392
|
+
<Tab href="/details">
|
|
393
|
+
Details
|
|
394
|
+
</Tab>
|
|
395
|
+
<Tab href="/settings">
|
|
396
|
+
Settings
|
|
397
|
+
</Tab>
|
|
398
|
+
</Tabs>
|
|
380
399
|
```
|
|
381
400
|
|
|
382
401
|
**Стили:**
|
package/package.json
CHANGED
package/src/components/Card.css
CHANGED
package/src/components/Tabs.css
CHANGED
|
@@ -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 {
|
package/src/components/Tabs.jsx
CHANGED
|
@@ -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,31 @@ 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
|
+
return (
|
|
38
|
+
<a
|
|
39
|
+
role="tab"
|
|
40
|
+
aria-current={selected ? 'page' : undefined}
|
|
41
|
+
aria-disabled={disabled ? 'true' : undefined}
|
|
42
|
+
className={className}
|
|
43
|
+
href={disabled ? undefined : href}
|
|
44
|
+
onClick={disabled ? (e) => e.preventDefault() : onClick}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
{content}
|
|
48
|
+
</a>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Render as button (default)
|
|
28
53
|
return (
|
|
29
54
|
<button
|
|
30
55
|
role="tab"
|
|
@@ -34,8 +59,7 @@ export function Tab({ children, selected = false, onClick, size = 'medium', vari
|
|
|
34
59
|
disabled={disabled}
|
|
35
60
|
{...props}
|
|
36
61
|
>
|
|
37
|
-
{
|
|
38
|
-
{children}
|
|
62
|
+
{content}
|
|
39
63
|
</button>
|
|
40
64
|
);
|
|
41
65
|
}
|
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
.ds-tooltip--top {
|
|
26
26
|
bottom: calc(100% + 8px);
|
|
27
27
|
left: 50%;
|
|
28
|
-
transform: translateX(-50%);
|
|
28
|
+
transform: translateX(calc(-50% + var(--tooltip-offset-x, 0px)));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
.ds-tooltip--top::after {
|
|
32
32
|
content: '';
|
|
33
33
|
position: absolute;
|
|
34
34
|
top: 100%;
|
|
35
|
-
left: 50
|
|
35
|
+
left: calc(50% + var(--arrow-offset, 0px));
|
|
36
36
|
transform: translateX(-50%);
|
|
37
37
|
border: 4px solid transparent;
|
|
38
38
|
border-top-color: var(--color-border);
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
content: '';
|
|
43
43
|
position: absolute;
|
|
44
44
|
top: 100%;
|
|
45
|
-
left: 50
|
|
45
|
+
left: calc(50% + var(--arrow-offset, 0px));
|
|
46
46
|
transform: translateX(-50%);
|
|
47
47
|
border: 3px solid transparent;
|
|
48
48
|
border-top-color: var(--color-bg);
|
|
@@ -53,14 +53,14 @@
|
|
|
53
53
|
.ds-tooltip--bottom {
|
|
54
54
|
top: calc(100% + 8px);
|
|
55
55
|
left: 50%;
|
|
56
|
-
transform: translateX(-50%);
|
|
56
|
+
transform: translateX(calc(-50% + var(--tooltip-offset-x, 0px)));
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
.ds-tooltip--bottom::after {
|
|
60
60
|
content: '';
|
|
61
61
|
position: absolute;
|
|
62
62
|
bottom: 100%;
|
|
63
|
-
left: 50
|
|
63
|
+
left: calc(50% + var(--arrow-offset, 0px));
|
|
64
64
|
transform: translateX(-50%);
|
|
65
65
|
border: 4px solid transparent;
|
|
66
66
|
border-bottom-color: var(--color-border);
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
content: '';
|
|
71
71
|
position: absolute;
|
|
72
72
|
bottom: 100%;
|
|
73
|
-
left: 50
|
|
73
|
+
left: calc(50% + var(--arrow-offset, 0px));
|
|
74
74
|
transform: translateX(-50%);
|
|
75
75
|
border: 3px solid transparent;
|
|
76
76
|
border-bottom-color: var(--color-bg);
|
|
@@ -9,6 +9,7 @@ export function Tooltip({
|
|
|
9
9
|
}) {
|
|
10
10
|
const [isVisible, setIsVisible] = useState(false);
|
|
11
11
|
const [computedPosition, setComputedPosition] = useState(position || 'top');
|
|
12
|
+
const [offset, setOffset] = useState({ x: 0, arrowOffset: 0 });
|
|
12
13
|
const wrapperRef = useRef(null);
|
|
13
14
|
const tooltipRef = useRef(null);
|
|
14
15
|
|
|
@@ -50,6 +51,36 @@ export function Tooltip({
|
|
|
50
51
|
}
|
|
51
52
|
}, [isVisible, position]);
|
|
52
53
|
|
|
54
|
+
// Check viewport boundaries and adjust position
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (isVisible && tooltipRef.current && wrapperRef.current) {
|
|
57
|
+
const tooltipRect = tooltipRef.current.getBoundingClientRect();
|
|
58
|
+
const viewportWidth = window.innerWidth;
|
|
59
|
+
const PADDING = 8; // minimum distance from viewport edge
|
|
60
|
+
|
|
61
|
+
let xOffset = 0;
|
|
62
|
+
let arrowShift = 0;
|
|
63
|
+
|
|
64
|
+
// For top/bottom positions, check horizontal overflow
|
|
65
|
+
if (computedPosition === 'top' || computedPosition === 'bottom') {
|
|
66
|
+
// Check if tooltip overflows on the right
|
|
67
|
+
if (tooltipRect.right > viewportWidth - PADDING) {
|
|
68
|
+
xOffset = viewportWidth - PADDING - tooltipRect.right;
|
|
69
|
+
arrowShift = -xOffset; // arrow moves opposite direction
|
|
70
|
+
}
|
|
71
|
+
// Check if tooltip overflows on the left
|
|
72
|
+
else if (tooltipRect.left < PADDING) {
|
|
73
|
+
xOffset = PADDING - tooltipRect.left;
|
|
74
|
+
arrowShift = -xOffset; // arrow moves opposite direction
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
setOffset({ x: xOffset, arrowOffset: arrowShift });
|
|
79
|
+
} else {
|
|
80
|
+
setOffset({ x: 0, arrowOffset: 0 });
|
|
81
|
+
}
|
|
82
|
+
}, [isVisible, computedPosition]);
|
|
83
|
+
|
|
53
84
|
if (!content) return children;
|
|
54
85
|
|
|
55
86
|
return (
|
|
@@ -66,6 +97,10 @@ export function Tooltip({
|
|
|
66
97
|
ref={tooltipRef}
|
|
67
98
|
className={`ds-tooltip ds-tooltip--${computedPosition}`}
|
|
68
99
|
role="tooltip"
|
|
100
|
+
style={{
|
|
101
|
+
'--tooltip-offset-x': `${offset.x}px`,
|
|
102
|
+
'--arrow-offset': `${offset.arrowOffset}px`,
|
|
103
|
+
}}
|
|
69
104
|
>
|
|
70
105
|
{content}
|
|
71
106
|
</div>
|
package/src/styles/iconSizes.js
CHANGED