@donkit-ai/design-system 0.2.16 → 0.2.18

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
@@ -41,7 +41,8 @@ npm install @donkit-ai/design-system@latest
41
41
  - **Tabs** - вкладки с состоянием selected в трех размерах (small, medium, large). Поддерживает `href` для рендера как ссылка
42
42
  - **Input** - текстовые поля с поддержкой иконок, ошибок и подсказок (small, medium)
43
43
  - **Stepper** - числовое поле с кнопками +/- для изменения значения (small, medium)
44
- - **Toggle** - переключатель для включения/выключения опций (small, medium)
44
+ - **Toggle** - переключатель для включения/выключения опций (xs, small, medium, large)
45
+ - **Checkbox** - чекбокс для выбора опций (xs, small, medium, large)
45
46
  - **Card** - карточки двух типов: info (информационная, прозрачный фон) и interactive (интерактивная с hover эффектом)
46
47
  - **Typography** - H1-H4, P1-P3 компоненты
47
48
  - **Code** - inline и block код с monospace шрифтом
@@ -550,7 +551,7 @@ import { AlertCircle } from 'lucide-react';
550
551
 
551
552
  ### Toggle
552
553
 
553
- Переключатель для включения/выключения опций. Высота совпадает с другими элементами интерфейса.
554
+ Переключатель для включения/выключения опций. Размеры привязаны к размерам иконок.
554
555
 
555
556
  ```jsx
556
557
  // Basic toggle
@@ -560,7 +561,15 @@ import { AlertCircle } from 'lucide-react';
560
561
  label="Enable notifications"
561
562
  />
562
563
 
563
- // Small size
564
+ // Extra Small size (16px)
565
+ <Toggle
566
+ size="xs"
567
+ checked={isCompact}
568
+ onChange={setIsCompact}
569
+ label="Compact mode"
570
+ />
571
+
572
+ // Small size (20px)
564
573
  <Toggle
565
574
  size="small"
566
575
  checked={isEnabled}
@@ -568,7 +577,7 @@ import { AlertCircle } from 'lucide-react';
568
577
  label="Auto-save"
569
578
  />
570
579
 
571
- // Medium size (default)
580
+ // Medium size (24px, default)
572
581
  <Toggle
573
582
  size="medium"
574
583
  checked={darkMode}
@@ -576,6 +585,14 @@ import { AlertCircle } from 'lucide-react';
576
585
  label="Dark mode"
577
586
  />
578
587
 
588
+ // Large size (28px)
589
+ <Toggle
590
+ size="large"
591
+ checked={isEnabled}
592
+ onChange={setIsEnabled}
593
+ label="Enable feature"
594
+ />
595
+
579
596
  // Without label (for tables/cards where context is clear)
580
597
  <Toggle
581
598
  checked={isEnabled}
@@ -601,17 +618,97 @@ import { AlertCircle } from 'lucide-react';
601
618
  **Параметры:**
602
619
  - `checked` - состояние переключателя (true/false)
603
620
  - `onChange` - функция обработки изменения состояния, получает новое значение (boolean)
604
- - `size` - размер: "small" или "medium" (default: "medium")
621
+ - `size` - размер: "xs", "small", "medium" или "large" (default: "medium")
605
622
  - `label` - текст подписи (опционально)
606
623
  - `disabled` - отключить переключатель
607
624
  - `id` - пользовательский ID (по умолчанию генерируется автоматически)
608
625
 
609
626
  **Стиль:**
610
- - Высота совпадает с Input, Select, Stepper (`--height-s` / `--height-m`)
627
+ - Размеры привязаны к иконкам: xs (16px), small (20px), medium (24px), large (28px)
611
628
  - Ширина трека: 1.75x от высоты
612
629
  - Включенное состояние: фон `--color-status-success`, border `--color-status-success` (зеленый)
613
630
  - Выключенное состояние: фон `--color-item-bg`, border `--color-border`, hover `--color-border-hover`
614
631
 
632
+ ### Checkbox
633
+
634
+ Чекбокс для выбора опций. Размеры привязаны к размерам иконок.
635
+
636
+ ```jsx
637
+ // Basic checkbox
638
+ <Checkbox
639
+ checked={isAccepted}
640
+ onChange={setIsAccepted}
641
+ label="Accept terms and conditions"
642
+ />
643
+
644
+ // Extra Small size (16px)
645
+ <Checkbox
646
+ size="xs"
647
+ checked={isCompact}
648
+ onChange={setIsCompact}
649
+ label="Compact view"
650
+ />
651
+
652
+ // Small size (20px)
653
+ <Checkbox
654
+ size="small"
655
+ checked={isEnabled}
656
+ onChange={setIsEnabled}
657
+ label="Enable feature"
658
+ />
659
+
660
+ // Medium size (24px, default)
661
+ <Checkbox
662
+ size="medium"
663
+ checked={isSubscribed}
664
+ onChange={setIsSubscribed}
665
+ label="Subscribe to newsletter"
666
+ />
667
+
668
+ // Large size (28px)
669
+ <Checkbox
670
+ size="large"
671
+ checked={isAccepted}
672
+ onChange={setIsAccepted}
673
+ label="I accept the terms"
674
+ />
675
+
676
+ // Without label (for tables/lists where context is clear)
677
+ <Checkbox
678
+ checked={isSelected}
679
+ onChange={setIsSelected}
680
+ />
681
+
682
+ // Disabled states
683
+ <Checkbox
684
+ checked={false}
685
+ onChange={() => {}}
686
+ label="Disabled unchecked"
687
+ disabled
688
+ />
689
+
690
+ <Checkbox
691
+ checked={true}
692
+ onChange={() => {}}
693
+ label="Disabled checked"
694
+ disabled
695
+ />
696
+ ```
697
+
698
+ **Параметры:**
699
+ - `checked` - состояние чекбокса (true/false)
700
+ - `onChange` - функция обработки изменения состояния, получает новое значение (boolean)
701
+ - `size` - размер: "xs", "small", "medium" или "large" (default: "medium")
702
+ - `label` - текст подписи (опционально)
703
+ - `disabled` - отключить чекбокс
704
+ - `id` - пользовательский ID (по умолчанию генерируется автоматически)
705
+
706
+ **Стиль:**
707
+ - Размеры привязаны к иконкам: xs (16px), small (20px), medium (24px), large (28px)
708
+ - Выбранное состояние: фон `--color-status-success`, border `--color-status-success` (зеленый)
709
+ - Невыбранное состояние: фон `--color-item-bg`, border `--color-border`, hover `--color-border-hover`
710
+ - Иконка галочки (Check) белого цвета при checked
711
+
615
712
  ### Card
616
713
 
617
714
  Карточки бывают двух типов:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkit-ai/design-system",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
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,4 @@
1
1
  .ds-accordion {
2
- border: 1px solid var(--color-border);
3
2
  border-radius: var(--radius-s);
4
3
  overflow: hidden;
5
4
  }
@@ -39,5 +38,5 @@
39
38
 
40
39
  .ds-accordion__content {
41
40
  padding: var(--space-m);
42
- border-top: 1px solid var(--color-border);
41
+ background-color: var(--color-item-bg);
43
42
  }
@@ -0,0 +1,88 @@
1
+ .ds-checkbox {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: var(--space-s);
5
+ cursor: pointer;
6
+ }
7
+
8
+ .ds-checkbox--disabled {
9
+ opacity: 0.5;
10
+ cursor: not-allowed;
11
+ }
12
+
13
+ .ds-checkbox__input {
14
+ position: absolute;
15
+ opacity: 0;
16
+ pointer-events: none;
17
+ }
18
+
19
+ .ds-checkbox__box {
20
+ position: relative;
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ background-color: var(--color-item-bg);
25
+ border: 1px solid var(--color-border);
26
+ transition: background-color var(--transition-normal), border-color var(--transition-normal);
27
+ flex-shrink: 0;
28
+ border-radius: 4px;
29
+ }
30
+
31
+ .ds-checkbox__input:checked + .ds-checkbox__box {
32
+ background-color: var(--color-status-success);
33
+ border-color: var(--color-status-success);
34
+ }
35
+
36
+ .ds-checkbox__input:not(:checked) + .ds-checkbox__box:hover {
37
+ border-color: var(--color-border-hover);
38
+ }
39
+
40
+ .ds-checkbox__icon {
41
+ color: var(--color-white);
42
+ }
43
+
44
+ .ds-checkbox__label {
45
+ font-size: var(--font-size-p1);
46
+ color: var(--color-txt-icon-1);
47
+ user-select: none;
48
+ }
49
+
50
+ /* Extra Small */
51
+ .ds-checkbox--xs .ds-checkbox__box {
52
+ width: var(--icon-xs);
53
+ height: var(--icon-xs);
54
+ }
55
+
56
+ .ds-checkbox--xs .ds-checkbox__label {
57
+ font-size: var(--font-size-p3);
58
+ }
59
+
60
+ /* Small */
61
+ .ds-checkbox--small .ds-checkbox__box {
62
+ width: var(--icon-s);
63
+ height: var(--icon-s);
64
+ }
65
+
66
+ .ds-checkbox--small .ds-checkbox__label {
67
+ font-size: var(--font-size-p2);
68
+ }
69
+
70
+ /* Medium */
71
+ .ds-checkbox--medium .ds-checkbox__box {
72
+ width: var(--icon-m);
73
+ height: var(--icon-m);
74
+ }
75
+
76
+ .ds-checkbox--medium .ds-checkbox__label {
77
+ font-size: var(--font-size-p1);
78
+ }
79
+
80
+ /* Large */
81
+ .ds-checkbox--large .ds-checkbox__box {
82
+ width: var(--icon-l);
83
+ height: var(--icon-l);
84
+ }
85
+
86
+ .ds-checkbox--large .ds-checkbox__label {
87
+ font-size: var(--font-size-p1);
88
+ }
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { Check } from 'lucide-react';
3
+ import './Checkbox.css';
4
+
5
+ export function Checkbox({
6
+ checked = false,
7
+ onChange,
8
+ size = 'medium',
9
+ disabled = false,
10
+ label,
11
+ id,
12
+ ...props
13
+ }) {
14
+ const checkboxId = id || `checkbox-${React.useId()}`;
15
+
16
+ const className = [
17
+ 'ds-checkbox',
18
+ `ds-checkbox--${size}`,
19
+ disabled && 'ds-checkbox--disabled',
20
+ ].filter(Boolean).join(' ');
21
+
22
+ const iconSize = size === 'xs' ? 10 : size === 'small' ? 14 : size === 'large' ? 20 : 16;
23
+
24
+ return (
25
+ <label className={className} htmlFor={checkboxId}>
26
+ <input
27
+ type="checkbox"
28
+ id={checkboxId}
29
+ className="ds-checkbox__input"
30
+ checked={checked}
31
+ onChange={(e) => onChange?.(e.target.checked)}
32
+ disabled={disabled}
33
+ {...props}
34
+ />
35
+ <span className="ds-checkbox__box">
36
+ {checked && (
37
+ <Check
38
+ size={iconSize}
39
+ strokeWidth={2.5}
40
+ className="ds-checkbox__icon"
41
+ />
42
+ )}
43
+ </span>
44
+ {label && <span className="ds-checkbox__label">{label}</span>}
45
+ </label>
46
+ );
47
+ }
@@ -1,5 +1,4 @@
1
1
  .ds-code-accordion {
2
- border: 1px solid var(--color-border);
3
2
  border-radius: var(--radius-s);
4
3
  overflow: hidden;
5
4
  }
@@ -43,7 +42,6 @@
43
42
  background-color: var(--color-code-bg);
44
43
  color: var(--color-txt-icon-1);
45
44
  padding: var(--space-xs);
46
- border-top: 1px solid var(--color-border);
47
45
  overflow-x: auto;
48
46
  line-height: 1.6;
49
47
  margin: 0;
@@ -65,4 +65,6 @@
65
65
  background-color: var(--color-bg);
66
66
  position: relative;
67
67
  z-index: 10;
68
+ border-bottom-left-radius: var(--space-s);
69
+ border-bottom-right-radius: var(--space-s);
68
70
  }
@@ -48,21 +48,42 @@
48
48
  user-select: none;
49
49
  }
50
50
 
51
+ /* Extra Small */
52
+ .ds-toggle--xs .ds-toggle__track {
53
+ width: calc(var(--icon-xs) * 1.75);
54
+ height: var(--icon-xs);
55
+ border-radius: calc(var(--icon-xs) / 2);
56
+ padding: 2px;
57
+ }
58
+
59
+ .ds-toggle--xs .ds-toggle__thumb {
60
+ width: calc(var(--icon-xs) - 4px);
61
+ height: calc(var(--icon-xs) - 4px);
62
+ }
63
+
64
+ .ds-toggle--xs .ds-toggle__input:checked + .ds-toggle__track .ds-toggle__thumb {
65
+ transform: translateX(calc(var(--icon-xs) * 0.75));
66
+ }
67
+
68
+ .ds-toggle--xs .ds-toggle__label {
69
+ font-size: var(--font-size-p3);
70
+ }
71
+
51
72
  /* Small */
52
73
  .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;
74
+ width: calc(var(--icon-s) * 1.75);
75
+ height: var(--icon-s);
76
+ border-radius: calc(var(--icon-s) / 2);
77
+ padding: 2px;
57
78
  }
58
79
 
59
80
  .ds-toggle--small .ds-toggle__thumb {
60
- width: calc(var(--height-s) - 8px);
61
- height: calc(var(--height-s) - 8px);
81
+ width: calc(var(--icon-s) - 4px);
82
+ height: calc(var(--icon-s) - 4px);
62
83
  }
63
84
 
64
85
  .ds-toggle--small .ds-toggle__input:checked + .ds-toggle__track .ds-toggle__thumb {
65
- transform: translateX(calc(var(--height-s) * 0.75));
86
+ transform: translateX(calc(var(--icon-s) * 0.75));
66
87
  }
67
88
 
68
89
  .ds-toggle--small .ds-toggle__label {
@@ -71,21 +92,42 @@
71
92
 
72
93
  /* Medium */
73
94
  .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;
95
+ width: calc(var(--icon-m) * 1.75);
96
+ height: var(--icon-m);
97
+ border-radius: calc(var(--icon-m) / 2);
98
+ padding: 2px;
78
99
  }
79
100
 
80
101
  .ds-toggle--medium .ds-toggle__thumb {
81
- width: calc(var(--height-m) - 10px);
82
- height: calc(var(--height-m) - 10px);
102
+ width: calc(var(--icon-m) - 4px);
103
+ height: calc(var(--icon-m) - 4px);
83
104
  }
84
105
 
85
106
  .ds-toggle--medium .ds-toggle__input:checked + .ds-toggle__track .ds-toggle__thumb {
86
- transform: translateX(calc(var(--height-m) * 0.75));
107
+ transform: translateX(calc(var(--icon-m) * 0.75));
87
108
  }
88
109
 
89
110
  .ds-toggle--medium .ds-toggle__label {
90
111
  font-size: var(--font-size-p1);
91
112
  }
113
+
114
+ /* Large */
115
+ .ds-toggle--large .ds-toggle__track {
116
+ width: calc(var(--icon-l) * 1.75);
117
+ height: var(--icon-l);
118
+ border-radius: calc(var(--icon-l) / 2);
119
+ padding: 3px;
120
+ }
121
+
122
+ .ds-toggle--large .ds-toggle__thumb {
123
+ width: calc(var(--icon-l) - 6px);
124
+ height: calc(var(--icon-l) - 6px);
125
+ }
126
+
127
+ .ds-toggle--large .ds-toggle__input:checked + .ds-toggle__track .ds-toggle__thumb {
128
+ transform: translateX(calc(var(--icon-l) * 0.75));
129
+ }
130
+
131
+ .ds-toggle--large .ds-toggle__label {
132
+ font-size: var(--font-size-p1);
133
+ }
@@ -12,6 +12,7 @@ export function Tooltip({
12
12
  const [offset, setOffset] = useState({ x: 0, arrowOffset: 0 });
13
13
  const wrapperRef = useRef(null);
14
14
  const tooltipRef = useRef(null);
15
+ const isTouchDevice = useRef(false);
15
16
 
16
17
  useEffect(() => {
17
18
  if (isVisible && !position && wrapperRef.current && tooltipRef.current) {
@@ -81,14 +82,51 @@ export function Tooltip({
81
82
  }
82
83
  }, [isVisible, computedPosition]);
83
84
 
85
+ // Close tooltip when clicking outside (for touch interfaces)
86
+ useEffect(() => {
87
+ if (!isVisible) return;
88
+
89
+ const handleClickOutside = (event) => {
90
+ if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
91
+ setIsVisible(false);
92
+ }
93
+ };
94
+
95
+ document.addEventListener('touchstart', handleClickOutside);
96
+
97
+ return () => {
98
+ document.removeEventListener('touchstart', handleClickOutside);
99
+ };
100
+ }, [isVisible]);
101
+
102
+ const handleTouchStart = () => {
103
+ isTouchDevice.current = true;
104
+ setIsVisible((prev) => !prev);
105
+ };
106
+
107
+ const handleMouseEnter = () => {
108
+ // Ignore mouse events on touch devices
109
+ if (!isTouchDevice.current) {
110
+ setIsVisible(true);
111
+ }
112
+ };
113
+
114
+ const handleMouseLeave = () => {
115
+ // Ignore mouse events on touch devices
116
+ if (!isTouchDevice.current) {
117
+ setIsVisible(false);
118
+ }
119
+ };
120
+
84
121
  if (!content) return children;
85
122
 
86
123
  return (
87
124
  <div
88
125
  ref={wrapperRef}
89
126
  className="ds-tooltip-wrapper"
90
- onMouseEnter={() => setIsVisible(true)}
91
- onMouseLeave={() => setIsVisible(false)}
127
+ onMouseEnter={handleMouseEnter}
128
+ onMouseLeave={handleMouseLeave}
129
+ onTouchStart={handleTouchStart}
92
130
  {...props}
93
131
  >
94
132
  {children}
package/src/index.js CHANGED
@@ -20,3 +20,4 @@ export { Accordion } from './components/Accordion';
20
20
  export { CodeAccordion } from './components/CodeAccordion';
21
21
  export { Tooltip } from './components/Tooltip';
22
22
  export { Toggle } from './components/Toggle';
23
+ export { Checkbox } from './components/Checkbox';