@donkit-ai/design-system 0.2.11 → 0.2.13

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
@@ -89,53 +89,70 @@ function MyComponent() {
89
89
 
90
90
  #### Размеры иконок
91
91
 
92
- **Важно:** Размеры иконок фиксированные (не адаптивные) и зависят от контекста использования:
92
+ Размеры иконок стандартизированы с помощью констант:
93
93
 
94
- - **20px** - компактные элементы
94
+ ```javascript
95
+ import { iconSizes } from '@donkit-ai/design-system';
96
+
97
+ // iconSizes = { xs: 16, s: 20, m: 24, l: 28, xl: 32 }
98
+ ```
99
+
100
+ **Соответствие размеров компонентам:**
101
+
102
+ - **16px (xs)** - очень мелкие элементы
103
+ - **20px (s)** - компактные элементы
95
104
  - Small кнопки (`size="small"`)
96
105
  - Tabs (вкладки)
97
106
  - Modal (иконка закрытия)
107
+ - Accordion, CodeAccordion
98
108
 
99
- - **24px** - стандартные элементы
109
+ - **24px (m)** - стандартные элементы
100
110
  - Medium кнопки (`size="medium"`, по умолчанию)
101
111
  - Input (иконки в полях ввода)
102
112
  - Alert (иконки статусов)
113
+ - Select (иконка выпадающего списка)
103
114
 
104
- - **28px** - крупные элементы
115
+ - **28px (l)** - крупные элементы
105
116
  - Large кнопки (`size="large"`)
106
117
 
118
+ - **32px (xl)** - очень крупные элементы
119
+
107
120
  **Всегда используется `strokeWidth={1.5}`** для единообразия дизайна.
108
121
 
109
122
  #### Примеры использования
110
123
 
111
124
  ```javascript
112
125
  import { Mail, Search, Eye, EyeOff, AlertCircle, Check, X } from 'lucide-react';
126
+ import { iconSizes } from '@donkit-ai/design-system';
113
127
 
114
128
  // Small button / Tabs / Modal
115
- <Button size="small" icon={<Mail size={20} strokeWidth={1.5} />}>
129
+ <Button size="small" icon={<Mail size={iconSizes.s} strokeWidth={1.5} />}>
116
130
  Send
117
131
  </Button>
118
- <Tab icon={<AlertCircle size={20} strokeWidth={1.5} />}>
132
+ <Tab icon={<AlertCircle size={iconSizes.s} strokeWidth={1.5} />}>
119
133
  Alerts
120
134
  </Tab>
121
135
 
122
136
  // Medium button / Input / Alert
123
- <Button size="medium" icon={<Search size={24} strokeWidth={1.5} />}>
137
+ <Button size="medium" icon={<Search size={iconSizes.m} strokeWidth={1.5} />}>
124
138
  Search
125
139
  </Button>
126
140
  <Input
127
- icon={<Search size={24} strokeWidth={1.5} />}
141
+ icon={<Search size={iconSizes.m} strokeWidth={1.5} />}
128
142
  placeholder="Search..."
129
143
  />
130
144
  <Alert
131
145
  type="success"
132
- icon={<Check size={24} strokeWidth={1.5} />}
146
+ icon={<Check size={iconSizes.m} strokeWidth={1.5} />}
133
147
  />
134
148
 
135
149
  // Large button
136
- <Button size="large" icon={<Mail size={28} strokeWidth={1.5} />}>
150
+ <Button size="large" icon={<Mail size={iconSizes.l} strokeWidth={1.5} />}>
137
151
  Send Email
138
152
  </Button>
153
+
154
+ // Extra Large icons
155
+ <Mail size={iconSizes.xl} strokeWidth={1.5} />
139
156
  ```
140
157
 
141
158
  ### 3. Переключение темы
@@ -154,9 +171,9 @@ document.documentElement.setAttribute('data-theme', 'light');
154
171
 
155
172
  #### Базовые примитивы
156
173
  - `--color-white`: #FFFAFA
157
- - `--color-white-65/50/40/20/15/13/06`: rgba с прозрачностью 65%, 50%, 40%, 20%, 15%, 13%, 6%
174
+ - `--color-white-95/92/65/50/40/20/15/13/06`: rgba с прозрачностью 95%, 92%, 65%, 50%, 40%, 20%, 15%, 13%, 6%
158
175
  - `--color-black`: #0E0F11
159
- - `--color-black-65/60/50/40/20/10/08/04`: rgba с прозрачностью 65%, 60%, 50%, 40%, 20%, 10%, 8%, 4%
176
+ - `--color-black-95/65/60/50/40/20/10/08/04`: rgba с прозрачностью 95%, 65%, 60%, 50%, 40%, 20%, 10%, 8%, 4%
160
177
  - `--color-red`: #EA6464
161
178
  - `--color-red-90`: rgba с прозрачностью 90%
162
179
 
@@ -170,7 +187,7 @@ document.documentElement.setAttribute('data-theme', 'light');
170
187
  - `--color-border` - границы
171
188
  - `--color-border-hover` - границы при hover (Dark: white-40, Light: black-40)
172
189
  - `--color-border-selected` - границы выбранного элемента (Dark: white-50, Light: black-50)
173
- - `--color-txt-icon-1` - основной текст/иконки (100%)
190
+ - `--color-txt-icon-1` - основной текст/иконки (Dark: white-92, Light: black-95)
174
191
  - `--color-txt-icon-2` - вторичный текст/иконки (65%)
175
192
  - `--color-accent` - акцентный цвет
176
193
  - `--color-accent-hover` - акцент при hover
@@ -203,7 +220,7 @@ document.documentElement.setAttribute('data-theme', 'light');
203
220
  - `--font-size-h1`: 32px → 40px → 40px
204
221
  - `--font-size-h2`: 28px → 36px → 36px
205
222
  - `--font-size-h3`: 24px → 28px → 28px
206
- - `--font-size-h4`: 18px20px20px
223
+ - `--font-size-h4`: 20px24px24px
207
224
  - `--font-size-p1`: 16px → 18px → 18px
208
225
  - `--font-size-p2`: 14px → 16px → 16px
209
226
  - `--font-size-p3`: 12px → 14px → 14px
@@ -214,7 +231,7 @@ document.documentElement.setAttribute('data-theme', 'light');
214
231
  - Меньше 14px → +6% (0.06em)
215
232
 
216
233
  Адаптивные значения трекинга:
217
- - `--letter-spacing-h4`: 0.02em → 0 → 0 (18px / 20px / 20px)
234
+ - `--letter-spacing-h4`: 0 → 0 → 0 (20px / 24px / 24px - без трекинга)
218
235
  - `--letter-spacing-p1`: 0.02em → 0.02em → 0.02em (16px / 18px / 18px)
219
236
  - `--letter-spacing-p2`: 0.04em → 0.02em → 0.02em (14px / 16px / 16px)
220
237
  - `--letter-spacing-p3`: 0.06em → 0.04em → 0.04em (12px / 14px / 14px)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkit-ai/design-system",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "Donkit Design System - minimal design tokens and React components",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -1,5 +1,6 @@
1
1
  import React, { useState } from 'react';
2
2
  import { ChevronDown } from 'lucide-react';
3
+ import { iconSizes } from '../styles/iconSizes';
3
4
  import './Accordion.css';
4
5
 
5
6
  export function Accordion({
@@ -20,7 +21,7 @@ export function Accordion({
20
21
  >
21
22
  <span className="ds-accordion__title">{title}</span>
22
23
  <ChevronDown
23
- size={20}
24
+ size={iconSizes.s}
24
25
  strokeWidth={1.5}
25
26
  className={`ds-accordion__icon ${isExpanded ? 'ds-accordion__icon--expanded' : ''}`}
26
27
  />
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Info, CheckCircle, AlertTriangle, XCircle, X } from 'lucide-react';
3
+ import { iconSizes } from '../styles/iconSizes';
3
4
  import './Alert.css';
4
5
 
5
6
  const ICON_MAP = {
@@ -24,7 +25,7 @@ export function Alert({
24
25
  <div className={`ds-alert ds-alert--${variant} ${!title ? 'ds-alert--no-title' : ''}`} role={alertRole} {...props}>
25
26
  {Icon && (
26
27
  <div className="ds-alert__icon">
27
- <Icon size={24} strokeWidth={1.5} />
28
+ <Icon size={iconSizes.m} strokeWidth={1.5} />
28
29
  </div>
29
30
  )}
30
31
  <div className="ds-alert__content">
@@ -38,7 +39,7 @@ export function Alert({
38
39
  onClick={onClose}
39
40
  aria-label="Close alert"
40
41
  >
41
- <X size={24} strokeWidth={1.5} />
42
+ <X size={iconSizes.m} strokeWidth={1.5} />
42
43
  </button>
43
44
  )}
44
45
  </div>
@@ -1,5 +1,6 @@
1
1
  import React, { useState } from 'react';
2
2
  import { ChevronDown } from 'lucide-react';
3
+ import { iconSizes } from '../styles/iconSizes';
3
4
  import './CodeAccordion.css';
4
5
 
5
6
  export function CodeAccordion({
@@ -20,7 +21,7 @@ export function CodeAccordion({
20
21
  >
21
22
  <span className="ds-code-accordion__title">{title}</span>
22
23
  <ChevronDown
23
- size={20}
24
+ size={iconSizes.s}
24
25
  strokeWidth={1.5}
25
26
  className={`ds-code-accordion__icon ${isExpanded ? 'ds-code-accordion__icon--expanded' : ''}`}
26
27
  />
@@ -1,5 +1,6 @@
1
1
  import React, { useEffect, useRef } from 'react';
2
2
  import { X } from 'lucide-react';
3
+ import { iconSizes } from '../styles/iconSizes';
3
4
  import { Button } from './Button';
4
5
  import './Modal.css';
5
6
 
@@ -52,7 +53,7 @@ export function Modal({
52
53
  <Button
53
54
  variant="ghost"
54
55
  size="small"
55
- icon={<X size={20} strokeWidth={1.5} />}
56
+ icon={<X size={iconSizes.s} strokeWidth={1.5} />}
56
57
  onClick={onClose}
57
58
  aria-label="Close modal"
58
59
  />
@@ -1,5 +1,6 @@
1
1
  import React, { useState, useRef, useEffect } from 'react';
2
2
  import { ChevronDown } from 'lucide-react';
3
+ import { iconSizes } from '../styles/iconSizes';
3
4
  import './Select.css';
4
5
 
5
6
  export function Select({
@@ -52,7 +53,7 @@ export function Select({
52
53
  }, [isOpen]);
53
54
 
54
55
  const selectedOption = options.find(opt => opt.value === value);
55
- const iconSize = size === 'small' ? 20 : 24;
56
+ const iconSize = size === 'small' ? iconSizes.s : iconSizes.m;
56
57
 
57
58
  return (
58
59
  <div className={`ds-select-wrapper ${fullWidth ? 'ds-select-wrapper--full' : ''} ${disabled ? 'ds-select-wrapper--disabled' : ''}`}>
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Minus, Plus } from 'lucide-react';
3
+ import { iconSizes } from '../styles/iconSizes';
3
4
  import './Stepper.css';
4
5
 
5
6
  export function Stepper({
@@ -56,7 +57,7 @@ export function Stepper({
56
57
  disabled && 'ds-stepper--disabled',
57
58
  ].filter(Boolean).join(' ');
58
59
 
59
- const iconSize = size === 'small' ? 20 : 24;
60
+ const iconSize = size === 'small' ? iconSizes.s : iconSizes.m;
60
61
 
61
62
  return (
62
63
  <div className={className}>
@@ -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/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // Styles
2
2
  export * from './styles/tokens.css';
3
+ export { iconSizes } from './styles/iconSizes';
3
4
 
4
5
  // Components
5
6
  export { Button } from './components/Button';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Icon sizes matching CSS variables
3
+ * --icon-xs: 16px
4
+ * --icon-s: 20px
5
+ * --icon-m: 24px
6
+ * --icon-l: 28px
7
+ * --icon-xl: 32px
8
+ */
9
+ export const iconSizes = {
10
+ xs: 16,
11
+ s: 20,
12
+ m: 24,
13
+ l: 28,
14
+ xl: 32,
15
+ };
@@ -11,6 +11,7 @@
11
11
  /* Color primitives - white */
12
12
  --color-white: #FFFAFA;
13
13
  --color-white-95: rgba(255, 250, 250, 0.95);
14
+ --color-white-92: rgba(255, 250, 250, 0.92);
14
15
  --color-white-65: rgba(255, 250, 250, 0.65);
15
16
  --color-white-50: rgba(255, 250, 250, 0.5);
16
17
  --color-white-40: rgba(255, 250, 250, 0.4);
@@ -67,7 +68,7 @@
67
68
  --color-border: var(--color-white-20);
68
69
  --color-border-hover: var(--color-white-40);
69
70
  --color-border-selected: var(--color-white-50);
70
- --color-txt-icon-1: var(--color-white-95);
71
+ --color-txt-icon-1: var(--color-white-92);
71
72
  --color-txt-icon-2: var(--color-white-65);
72
73
  --color-accent: var(--color-red);
73
74
  --color-accent-hover: var(--color-red-90);
@@ -173,6 +174,13 @@
173
174
  --height-s: 32px;
174
175
  --height-m: 44px;
175
176
  --height-l: 56px;
177
+
178
+ /* Icon sizes */
179
+ --icon-xs: 16px;
180
+ --icon-s: 20px;
181
+ --icon-m: 24px;
182
+ --icon-l: 28px;
183
+ --icon-xl: 32px;
176
184
  }
177
185
 
178
186
  :root {
@@ -188,7 +196,7 @@
188
196
  --font-size-h1: 32px;
189
197
  --font-size-h2: 28px;
190
198
  --font-size-h3: 24px;
191
- --font-size-h4: 18px;
199
+ --font-size-h4: 20px;
192
200
  --font-size-p1: 16px;
193
201
  --font-size-p2: 14px;
194
202
  --font-size-p3: 12px;
@@ -197,7 +205,7 @@
197
205
  --letter-spacing-h1: 0;
198
206
  --letter-spacing-h2: 0;
199
207
  --letter-spacing-h3: 0;
200
- --letter-spacing-h4: 0.02em; /* 18px+2% */
208
+ --letter-spacing-h4: 0; /* 20pxno tracking */
201
209
  --letter-spacing-p1: 0.02em; /* 16px → +2% */
202
210
  --letter-spacing-p2: 0.04em; /* 14px → +4% */
203
211
  --letter-spacing-p3: 0.06em; /* 12px → +6% */
@@ -209,12 +217,12 @@
209
217
  --font-size-h1: 40px;
210
218
  --font-size-h2: 36px;
211
219
  --font-size-h3: 28px;
212
- --font-size-h4: 20px;
220
+ --font-size-h4: 24px;
213
221
  --font-size-p1: 18px;
214
222
  --font-size-p2: 16px;
215
223
  --font-size-p3: 14px;
216
224
 
217
- --letter-spacing-h4: 0; /* 20px → no tracking */
225
+ --letter-spacing-h4: 0; /* 24px → no tracking */
218
226
  --letter-spacing-p1: 0.02em; /* 18px → +2% */
219
227
  --letter-spacing-p2: 0.02em; /* 16px → +2% */
220
228
  --letter-spacing-p3: 0.04em; /* 14px → +4% */
@@ -227,12 +235,12 @@
227
235
  --font-size-h1: 40px;
228
236
  --font-size-h2: 36px;
229
237
  --font-size-h3: 28px;
230
- --font-size-h4: 20px;
238
+ --font-size-h4: 24px;
231
239
  --font-size-p1: 18px;
232
240
  --font-size-p2: 16px;
233
241
  --font-size-p3: 14px;
234
242
 
235
- --letter-spacing-h4: 0; /* 20px → no tracking */
243
+ --letter-spacing-h4: 0; /* 24px → no tracking */
236
244
  --letter-spacing-p1: 0.02em; /* 18px → +2% */
237
245
  --letter-spacing-p2: 0.02em; /* 16px → +2% */
238
246
  --letter-spacing-p3: 0.04em; /* 14px → +4% */