@donkit-ai/design-system 0.2.17 → 0.2.19
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 +281 -15
- package/package.json +1 -1
- package/src/components/Button.css +7 -0
- package/src/components/Card.css +9 -1
- package/src/components/Card.jsx +34 -1
- package/src/components/Checkbox.css +88 -0
- package/src/components/Checkbox.jsx +47 -0
- package/src/components/Input.css +23 -0
- package/src/components/Radio.css +115 -0
- package/src/components/Radio.jsx +42 -0
- package/src/components/Select.css +7 -0
- package/src/components/Select.jsx +1 -1
- package/src/components/Stepper.css +16 -0
- package/src/components/Stepper.jsx +1 -1
- package/src/components/Tabs.css +7 -0
- package/src/components/Textarea.css +6 -0
- package/src/components/Toggle.css +56 -14
- package/src/components/Tooltip.jsx +40 -2
- package/src/index.js +2 -0
- package/src/styles/tokens.css +1 -0
package/README.md
CHANGED
|
@@ -37,12 +37,16 @@ npm install @donkit-ai/design-system@latest
|
|
|
37
37
|
|
|
38
38
|
## Компоненты
|
|
39
39
|
|
|
40
|
-
- **Button** - кнопки с вариантами (primary, secondary, ghost) и размерами (small, medium, large). Поддерживает `href` для рендера как ссылка
|
|
41
|
-
- **Tabs** - вкладки с состоянием selected в
|
|
42
|
-
- **Input** - текстовые поля с поддержкой иконок, ошибок и подсказок (small, medium)
|
|
43
|
-
- **
|
|
44
|
-
- **
|
|
45
|
-
- **
|
|
40
|
+
- **Button** - кнопки с вариантами (primary, secondary, ghost) и размерами (xs, small, medium, large). Поддерживает `href` для рендера как ссылка
|
|
41
|
+
- **Tabs** - вкладки с состоянием selected в четырех размерах (xs, small, medium, large). Поддерживает `href` для рендера как ссылка
|
|
42
|
+
- **Input** - текстовые поля с поддержкой иконок, ошибок и подсказок (xs, small, medium)
|
|
43
|
+
- **Textarea** - многострочное текстовое поле (xs, small, medium)
|
|
44
|
+
- **Select** - выпадающий список с кастомным дизайном (xs, small, medium)
|
|
45
|
+
- **Stepper** - числовое поле с кнопками +/- для изменения значения (xs, small, medium)
|
|
46
|
+
- **Toggle** - переключатель для включения/выключения опций (xs, small, medium, large)
|
|
47
|
+
- **Checkbox** - чекбокс для выбора опций (xs, small, medium, large)
|
|
48
|
+
- **Radio** - радиокнопка для выбора одного варианта из группы (xs, small, medium, large)
|
|
49
|
+
- **Card** - карточки двух типов: info (информационная, прозрачный фон) и interactive (интерактивная с hover эффектом). Поддерживает `href` для рендера как ссылка
|
|
46
50
|
- **Typography** - H1-H4, P1-P3 компоненты
|
|
47
51
|
- **Code** - inline и block код с monospace шрифтом
|
|
48
52
|
- **Link** - ссылки акцентным цветом, при hover цвет меняется и появляется подчеркивание
|
|
@@ -112,20 +116,25 @@ import { iconSizes } from '@donkit-ai/design-system';
|
|
|
112
116
|
**Соответствие размеров компонентам:**
|
|
113
117
|
|
|
114
118
|
- **16px (xs)** - очень мелкие элементы
|
|
119
|
+
- Extra Small кнопки (`size="xs"`)
|
|
120
|
+
- Extra Small табы (`size="xs"`)
|
|
121
|
+
|
|
115
122
|
- **20px (s)** - компактные элементы
|
|
116
123
|
- Small кнопки (`size="small"`)
|
|
117
|
-
-
|
|
124
|
+
- Small табы (`size="small"`)
|
|
118
125
|
- Modal (иконка закрытия)
|
|
119
126
|
- Accordion, CodeAccordion
|
|
120
127
|
|
|
121
128
|
- **24px (m)** - стандартные элементы
|
|
122
129
|
- Medium кнопки (`size="medium"`, по умолчанию)
|
|
130
|
+
- Medium табы (`size="medium"`)
|
|
123
131
|
- Input (иконки в полях ввода)
|
|
124
132
|
- Alert (иконки статусов)
|
|
125
133
|
- Select (иконка выпадающего списка)
|
|
126
134
|
|
|
127
135
|
- **28px (l)** - крупные элементы
|
|
128
136
|
- Large кнопки (`size="large"`)
|
|
137
|
+
- Large табы (`size="large"`)
|
|
129
138
|
|
|
130
139
|
- **48px (xl)** - очень крупные элементы
|
|
131
140
|
|
|
@@ -333,6 +342,9 @@ document.documentElement.setAttribute('data-theme', 'light');
|
|
|
333
342
|
<Button variant="ghost">Ghost</Button>
|
|
334
343
|
|
|
335
344
|
// Размеры с соответствующими иконками
|
|
345
|
+
<Button size="xs" icon={<Mail size={16} strokeWidth={1.5} />}>
|
|
346
|
+
Extra Small
|
|
347
|
+
</Button>
|
|
336
348
|
<Button size="small" icon={<Mail size={20} strokeWidth={1.5} />}>
|
|
337
349
|
Small
|
|
338
350
|
</Button>
|
|
@@ -375,7 +387,8 @@ import { AlertCircle } from 'lucide-react';
|
|
|
375
387
|
</Tab>
|
|
376
388
|
</Tabs>
|
|
377
389
|
|
|
378
|
-
// With icons (
|
|
390
|
+
// With icons (размер иконки соответствует размеру таба)
|
|
391
|
+
// xs: 16px, small: 20px, medium: 24px, large: 28px
|
|
379
392
|
<Tabs size="small">
|
|
380
393
|
<Tab
|
|
381
394
|
selected={selectedTab === 'alerts'}
|
|
@@ -416,7 +429,7 @@ import { AlertCircle } from 'lucide-react';
|
|
|
416
429
|
- Hover: фон `--color-item-bg-hover`, текст `--color-txt-icon-1`
|
|
417
430
|
- Default: текст `--color-txt-icon-2`
|
|
418
431
|
|
|
419
|
-
**Размеры:** small, medium, large
|
|
432
|
+
**Размеры:** xs, small, medium, large
|
|
420
433
|
|
|
421
434
|
**Дополнительно:** поддержка иконок и disabled состояния
|
|
422
435
|
|
|
@@ -550,7 +563,7 @@ import { AlertCircle } from 'lucide-react';
|
|
|
550
563
|
|
|
551
564
|
### Toggle
|
|
552
565
|
|
|
553
|
-
Переключатель для включения/выключения опций.
|
|
566
|
+
Переключатель для включения/выключения опций. Размеры привязаны к размерам иконок.
|
|
554
567
|
|
|
555
568
|
```jsx
|
|
556
569
|
// Basic toggle
|
|
@@ -560,7 +573,15 @@ import { AlertCircle } from 'lucide-react';
|
|
|
560
573
|
label="Enable notifications"
|
|
561
574
|
/>
|
|
562
575
|
|
|
563
|
-
// Small size
|
|
576
|
+
// Extra Small size (16px)
|
|
577
|
+
<Toggle
|
|
578
|
+
size="xs"
|
|
579
|
+
checked={isCompact}
|
|
580
|
+
onChange={setIsCompact}
|
|
581
|
+
label="Compact mode"
|
|
582
|
+
/>
|
|
583
|
+
|
|
584
|
+
// Small size (20px)
|
|
564
585
|
<Toggle
|
|
565
586
|
size="small"
|
|
566
587
|
checked={isEnabled}
|
|
@@ -568,7 +589,7 @@ import { AlertCircle } from 'lucide-react';
|
|
|
568
589
|
label="Auto-save"
|
|
569
590
|
/>
|
|
570
591
|
|
|
571
|
-
// Medium size (default)
|
|
592
|
+
// Medium size (24px, default)
|
|
572
593
|
<Toggle
|
|
573
594
|
size="medium"
|
|
574
595
|
checked={darkMode}
|
|
@@ -576,6 +597,14 @@ import { AlertCircle } from 'lucide-react';
|
|
|
576
597
|
label="Dark mode"
|
|
577
598
|
/>
|
|
578
599
|
|
|
600
|
+
// Large size (28px)
|
|
601
|
+
<Toggle
|
|
602
|
+
size="large"
|
|
603
|
+
checked={isEnabled}
|
|
604
|
+
onChange={setIsEnabled}
|
|
605
|
+
label="Enable feature"
|
|
606
|
+
/>
|
|
607
|
+
|
|
579
608
|
// Without label (for tables/cards where context is clear)
|
|
580
609
|
<Toggle
|
|
581
610
|
checked={isEnabled}
|
|
@@ -601,17 +630,220 @@ import { AlertCircle } from 'lucide-react';
|
|
|
601
630
|
**Параметры:**
|
|
602
631
|
- `checked` - состояние переключателя (true/false)
|
|
603
632
|
- `onChange` - функция обработки изменения состояния, получает новое значение (boolean)
|
|
604
|
-
- `size` - размер: "small" или "
|
|
633
|
+
- `size` - размер: "xs", "small", "medium" или "large" (default: "medium")
|
|
605
634
|
- `label` - текст подписи (опционально)
|
|
606
635
|
- `disabled` - отключить переключатель
|
|
607
636
|
- `id` - пользовательский ID (по умолчанию генерируется автоматически)
|
|
608
637
|
|
|
609
638
|
**Стиль:**
|
|
610
|
-
-
|
|
639
|
+
- Размеры привязаны к иконкам: xs (16px), small (20px), medium (24px), large (28px)
|
|
611
640
|
- Ширина трека: 1.75x от высоты
|
|
612
641
|
- Включенное состояние: фон `--color-status-success`, border `--color-status-success` (зеленый)
|
|
613
642
|
- Выключенное состояние: фон `--color-item-bg`, border `--color-border`, hover `--color-border-hover`
|
|
614
643
|
|
|
644
|
+
### Checkbox
|
|
645
|
+
|
|
646
|
+
Чекбокс для выбора опций. Размеры привязаны к размерам иконок.
|
|
647
|
+
|
|
648
|
+
```jsx
|
|
649
|
+
// Basic checkbox
|
|
650
|
+
<Checkbox
|
|
651
|
+
checked={isAccepted}
|
|
652
|
+
onChange={setIsAccepted}
|
|
653
|
+
label="Accept terms and conditions"
|
|
654
|
+
/>
|
|
655
|
+
|
|
656
|
+
// Extra Small size (16px)
|
|
657
|
+
<Checkbox
|
|
658
|
+
size="xs"
|
|
659
|
+
checked={isCompact}
|
|
660
|
+
onChange={setIsCompact}
|
|
661
|
+
label="Compact view"
|
|
662
|
+
/>
|
|
663
|
+
|
|
664
|
+
// Small size (20px)
|
|
665
|
+
<Checkbox
|
|
666
|
+
size="small"
|
|
667
|
+
checked={isEnabled}
|
|
668
|
+
onChange={setIsEnabled}
|
|
669
|
+
label="Enable feature"
|
|
670
|
+
/>
|
|
671
|
+
|
|
672
|
+
// Medium size (24px, default)
|
|
673
|
+
<Checkbox
|
|
674
|
+
size="medium"
|
|
675
|
+
checked={isSubscribed}
|
|
676
|
+
onChange={setIsSubscribed}
|
|
677
|
+
label="Subscribe to newsletter"
|
|
678
|
+
/>
|
|
679
|
+
|
|
680
|
+
// Large size (28px)
|
|
681
|
+
<Checkbox
|
|
682
|
+
size="large"
|
|
683
|
+
checked={isAccepted}
|
|
684
|
+
onChange={setIsAccepted}
|
|
685
|
+
label="I accept the terms"
|
|
686
|
+
/>
|
|
687
|
+
|
|
688
|
+
// Without label (for tables/lists where context is clear)
|
|
689
|
+
<Checkbox
|
|
690
|
+
checked={isSelected}
|
|
691
|
+
onChange={setIsSelected}
|
|
692
|
+
/>
|
|
693
|
+
|
|
694
|
+
// Disabled states
|
|
695
|
+
<Checkbox
|
|
696
|
+
checked={false}
|
|
697
|
+
onChange={() => {}}
|
|
698
|
+
label="Disabled unchecked"
|
|
699
|
+
disabled
|
|
700
|
+
/>
|
|
701
|
+
|
|
702
|
+
<Checkbox
|
|
703
|
+
checked={true}
|
|
704
|
+
onChange={() => {}}
|
|
705
|
+
label="Disabled checked"
|
|
706
|
+
disabled
|
|
707
|
+
/>
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
**Параметры:**
|
|
711
|
+
- `checked` - состояние чекбокса (true/false)
|
|
712
|
+
- `onChange` - функция обработки изменения состояния, получает новое значение (boolean)
|
|
713
|
+
- `size` - размер: "xs", "small", "medium" или "large" (default: "medium")
|
|
714
|
+
- `label` - текст подписи (опционально)
|
|
715
|
+
- `disabled` - отключить чекбокс
|
|
716
|
+
- `id` - пользовательский ID (по умолчанию генерируется автоматически)
|
|
717
|
+
|
|
718
|
+
**Стиль:**
|
|
719
|
+
- Размеры привязаны к иконкам: xs (16px), small (20px), medium (24px), large (28px)
|
|
720
|
+
- Выбранное состояние: фон `--color-status-success`, border `--color-status-success` (зеленый)
|
|
721
|
+
- Невыбранное состояние: фон `--color-item-bg`, border `--color-border`, hover `--color-border-hover`
|
|
722
|
+
- Иконка галочки (Check) белого цвета при checked
|
|
723
|
+
|
|
724
|
+
### Radio
|
|
725
|
+
|
|
726
|
+
Радиокнопка для выбора одного варианта из группы. Размеры привязаны к размерам иконок.
|
|
727
|
+
|
|
728
|
+
```jsx
|
|
729
|
+
// Basic radio
|
|
730
|
+
<Radio
|
|
731
|
+
checked={selectedOption === 'option1'}
|
|
732
|
+
onChange={() => setSelectedOption('option1')}
|
|
733
|
+
name="options"
|
|
734
|
+
value="option1"
|
|
735
|
+
label="Option 1"
|
|
736
|
+
/>
|
|
737
|
+
|
|
738
|
+
// Extra Small size (16px)
|
|
739
|
+
<Radio
|
|
740
|
+
size="xs"
|
|
741
|
+
checked={size === 'xs'}
|
|
742
|
+
onChange={() => setSize('xs')}
|
|
743
|
+
name="size"
|
|
744
|
+
value="xs"
|
|
745
|
+
label="Extra Small"
|
|
746
|
+
/>
|
|
747
|
+
|
|
748
|
+
// Small size (20px)
|
|
749
|
+
<Radio
|
|
750
|
+
size="small"
|
|
751
|
+
checked={size === 'small'}
|
|
752
|
+
onChange={() => setSize('small')}
|
|
753
|
+
name="size"
|
|
754
|
+
value="small"
|
|
755
|
+
label="Small"
|
|
756
|
+
/>
|
|
757
|
+
|
|
758
|
+
// Medium size (24px, default)
|
|
759
|
+
<Radio
|
|
760
|
+
size="medium"
|
|
761
|
+
checked={size === 'medium'}
|
|
762
|
+
onChange={() => setSize('medium')}
|
|
763
|
+
name="size"
|
|
764
|
+
value="medium"
|
|
765
|
+
label="Medium"
|
|
766
|
+
/>
|
|
767
|
+
|
|
768
|
+
// Large size (28px)
|
|
769
|
+
<Radio
|
|
770
|
+
size="large"
|
|
771
|
+
checked={size === 'large'}
|
|
772
|
+
onChange={() => setSize('large')}
|
|
773
|
+
name="size"
|
|
774
|
+
value="large"
|
|
775
|
+
label="Large"
|
|
776
|
+
/>
|
|
777
|
+
|
|
778
|
+
// Radio group example
|
|
779
|
+
<div>
|
|
780
|
+
<Radio
|
|
781
|
+
checked={paymentMethod === 'card'}
|
|
782
|
+
onChange={() => setPaymentMethod('card')}
|
|
783
|
+
name="payment"
|
|
784
|
+
value="card"
|
|
785
|
+
label="Credit Card"
|
|
786
|
+
/>
|
|
787
|
+
<Radio
|
|
788
|
+
checked={paymentMethod === 'paypal'}
|
|
789
|
+
onChange={() => setPaymentMethod('paypal')}
|
|
790
|
+
name="payment"
|
|
791
|
+
value="paypal"
|
|
792
|
+
label="PayPal"
|
|
793
|
+
/>
|
|
794
|
+
<Radio
|
|
795
|
+
checked={paymentMethod === 'bank'}
|
|
796
|
+
onChange={() => setPaymentMethod('bank')}
|
|
797
|
+
name="payment"
|
|
798
|
+
value="bank"
|
|
799
|
+
label="Bank Transfer"
|
|
800
|
+
/>
|
|
801
|
+
</div>
|
|
802
|
+
|
|
803
|
+
// Without label (for tables/lists where context is clear)
|
|
804
|
+
<Radio
|
|
805
|
+
checked={isSelected}
|
|
806
|
+
onChange={() => setIsSelected(true)}
|
|
807
|
+
name="selection"
|
|
808
|
+
value="item1"
|
|
809
|
+
/>
|
|
810
|
+
|
|
811
|
+
// Disabled states
|
|
812
|
+
<Radio
|
|
813
|
+
checked={false}
|
|
814
|
+
onChange={() => {}}
|
|
815
|
+
name="disabled-group"
|
|
816
|
+
value="disabled1"
|
|
817
|
+
label="Disabled unchecked"
|
|
818
|
+
disabled
|
|
819
|
+
/>
|
|
820
|
+
|
|
821
|
+
<Radio
|
|
822
|
+
checked={true}
|
|
823
|
+
onChange={() => {}}
|
|
824
|
+
name="disabled-group"
|
|
825
|
+
value="disabled2"
|
|
826
|
+
label="Disabled checked"
|
|
827
|
+
disabled
|
|
828
|
+
/>
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
**Параметры:**
|
|
832
|
+
- `checked` - состояние радиокнопки (true/false)
|
|
833
|
+
- `onChange` - функция обработки изменения состояния, получает новое значение (boolean)
|
|
834
|
+
- `name` - имя группы радиокнопок (обязательно для группировки)
|
|
835
|
+
- `value` - значение радиокнопки
|
|
836
|
+
- `size` - размер: "xs", "small", "medium" или "large" (default: "medium")
|
|
837
|
+
- `label` - текст подписи (опционально)
|
|
838
|
+
- `disabled` - отключить радиокнопку
|
|
839
|
+
- `id` - пользовательский ID (по умолчанию генерируется автоматически)
|
|
840
|
+
|
|
841
|
+
**Стиль:**
|
|
842
|
+
- Размеры привязаны к иконкам: xs (16px), small (20px), medium (24px), large (28px)
|
|
843
|
+
- Выбранное состояние: фон `--color-status-success`, border `--color-status-success` (зеленый), белая точка внутри
|
|
844
|
+
- Невыбранное состояние: фон `--color-item-bg`, border `--color-border`, hover `--color-border-hover`
|
|
845
|
+
- Круглая форма (border-radius: 50%)
|
|
846
|
+
|
|
615
847
|
### Card
|
|
616
848
|
|
|
617
849
|
Карточки бывают двух типов:
|
|
@@ -637,11 +869,45 @@ import { AlertCircle } from 'lucide-react';
|
|
|
637
869
|
<Card padding="small">Small padding</Card>
|
|
638
870
|
<Card padding="medium">Medium padding</Card>
|
|
639
871
|
<Card padding="large">Large padding</Card>
|
|
872
|
+
|
|
873
|
+
// Card как ссылка (рендерит <a> вместо <div>)
|
|
874
|
+
// При обычном клике: вызывается onClick (preventDefault автоматический)
|
|
875
|
+
// При Cmd/Ctrl+Click или Middle Click: открывается новая вкладка
|
|
876
|
+
<Card
|
|
877
|
+
padding="medium"
|
|
878
|
+
variant="interactive"
|
|
879
|
+
href="/dashboard"
|
|
880
|
+
onClick={() => navigate('/dashboard')}
|
|
881
|
+
>
|
|
882
|
+
<H4>Dashboard</H4>
|
|
883
|
+
<P1>Go to dashboard page</P1>
|
|
884
|
+
</Card>
|
|
885
|
+
|
|
886
|
+
<Card
|
|
887
|
+
padding="medium"
|
|
888
|
+
variant="interactive"
|
|
889
|
+
href="https://example.com"
|
|
890
|
+
>
|
|
891
|
+
<H4>External Link</H4>
|
|
892
|
+
<P1>Visit external site</P1>
|
|
893
|
+
</Card>
|
|
894
|
+
|
|
895
|
+
// Disabled Card Link
|
|
896
|
+
<Card
|
|
897
|
+
padding="medium"
|
|
898
|
+
variant="interactive"
|
|
899
|
+
href="/disabled"
|
|
900
|
+
disabled
|
|
901
|
+
>
|
|
902
|
+
<H4>Disabled</H4>
|
|
903
|
+
<P1>This card is disabled</P1>
|
|
904
|
+
</Card>
|
|
640
905
|
```
|
|
641
906
|
|
|
642
907
|
**Стиль:**
|
|
643
908
|
- **variant="info"** (по умолчанию): прозрачный фон, border `--color-border`
|
|
644
|
-
- **variant="interactive"**: фон `--color-item-bg`, при hover фон `--color-item-bg-hover
|
|
909
|
+
- **variant="interactive"**: фон `--color-item-bg`, при hover фон `--color-item-bg-hover`, курсор pointer
|
|
910
|
+
- Поддержка `href` для рендера как ссылка с умной обработкой кликов
|
|
645
911
|
|
|
646
912
|
### Code
|
|
647
913
|
|
package/package.json
CHANGED
|
@@ -61,6 +61,13 @@
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/* Sizes */
|
|
64
|
+
.ds-button--xs {
|
|
65
|
+
height: var(--height-xs);
|
|
66
|
+
padding: 0 calc(var(--height-xs) / 2);
|
|
67
|
+
font-size: var(--font-size-p3);
|
|
68
|
+
border-radius: var(--radius-xs);
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
.ds-button--small {
|
|
65
72
|
height: var(--height-s);
|
|
66
73
|
padding: 0 calc(var(--height-s) / 2);
|
package/src/components/Card.css
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
border-radius: var(--radius-s);
|
|
4
4
|
border: 1px solid var(--color-border);
|
|
5
5
|
transition: border-color var(--transition-normal), background-color var(--transition-normal);
|
|
6
|
+
text-decoration: none;
|
|
7
|
+
color: inherit;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
.ds-card--interactive {
|
|
@@ -11,10 +13,16 @@
|
|
|
11
13
|
border: none;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
.ds-card--interactive:hover {
|
|
16
|
+
.ds-card--interactive:hover:not([aria-disabled="true"]) {
|
|
15
17
|
background-color: var(--color-item-bg-hover);
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
.ds-card[aria-disabled="true"] {
|
|
21
|
+
opacity: 0.5;
|
|
22
|
+
cursor: not-allowed;
|
|
23
|
+
pointer-events: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
18
26
|
.ds-card--none {
|
|
19
27
|
padding: 0;
|
|
20
28
|
}
|
package/src/components/Card.jsx
CHANGED
|
@@ -7,10 +7,12 @@ export function Card({
|
|
|
7
7
|
variant = 'info',
|
|
8
8
|
hover = false, // deprecated, use variant="interactive"
|
|
9
9
|
onClick,
|
|
10
|
+
href,
|
|
11
|
+
disabled = false,
|
|
10
12
|
...props
|
|
11
13
|
}) {
|
|
12
14
|
const cardVariant = hover ? 'interactive' : variant;
|
|
13
|
-
const isInteractive = cardVariant === 'interactive' || onClick;
|
|
15
|
+
const isInteractive = cardVariant === 'interactive' || onClick || href;
|
|
14
16
|
|
|
15
17
|
const className = [
|
|
16
18
|
'ds-card',
|
|
@@ -18,10 +20,41 @@ export function Card({
|
|
|
18
20
|
isInteractive && 'ds-card--interactive',
|
|
19
21
|
].filter(Boolean).join(' ');
|
|
20
22
|
|
|
23
|
+
// Render as link if href is provided
|
|
24
|
+
if (href) {
|
|
25
|
+
const handleClick = (e) => {
|
|
26
|
+
if (disabled) {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// При Cmd/Ctrl+Click или Middle Click — пусть браузер откроет новую вкладку
|
|
31
|
+
if (e.metaKey || e.ctrlKey || e.button === 1) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Обычный клик — preventDefault и вызов onClick
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
onClick?.(e);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<a
|
|
41
|
+
className={className}
|
|
42
|
+
href={disabled ? undefined : href}
|
|
43
|
+
onClick={handleClick}
|
|
44
|
+
aria-disabled={disabled ? 'true' : undefined}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
{children}
|
|
48
|
+
</a>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Render as button if interactive with onClick
|
|
21
53
|
const Element = isInteractive && onClick ? 'button' : 'div';
|
|
22
54
|
const interactiveProps = isInteractive && onClick ? {
|
|
23
55
|
type: 'button',
|
|
24
56
|
onClick,
|
|
57
|
+
disabled,
|
|
25
58
|
} : {};
|
|
26
59
|
|
|
27
60
|
return (
|
|
@@ -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
|
+
}
|
package/src/components/Input.css
CHANGED
|
@@ -58,6 +58,13 @@
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/* Sizes */
|
|
61
|
+
.ds-input--xs {
|
|
62
|
+
height: var(--height-xs);
|
|
63
|
+
padding: 0 calc(var(--height-xs) / 4);
|
|
64
|
+
font-size: var(--font-size-p3);
|
|
65
|
+
border-radius: var(--radius-xs);
|
|
66
|
+
}
|
|
67
|
+
|
|
61
68
|
.ds-input--small {
|
|
62
69
|
height: var(--height-s);
|
|
63
70
|
padding: 0 calc(var(--height-s) / 4);
|
|
@@ -72,6 +79,10 @@
|
|
|
72
79
|
border-radius: var(--radius-s);
|
|
73
80
|
}
|
|
74
81
|
|
|
82
|
+
.ds-input--with-icon.ds-input--xs {
|
|
83
|
+
padding-left: calc(var(--height-xs) / 4 + 16px + var(--height-xs) / 4);
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
.ds-input--with-icon.ds-input--small {
|
|
76
87
|
padding-left: calc(var(--height-s) / 4 + 20px + var(--height-s) / 4);
|
|
77
88
|
}
|
|
@@ -80,6 +91,10 @@
|
|
|
80
91
|
padding-left: calc(var(--height-m) / 4 + 24px + var(--height-m) / 4);
|
|
81
92
|
}
|
|
82
93
|
|
|
94
|
+
.ds-input--with-icon-right.ds-input--xs {
|
|
95
|
+
padding-right: calc(var(--height-xs) / 4 + 16px + var(--height-xs) / 4);
|
|
96
|
+
}
|
|
97
|
+
|
|
83
98
|
.ds-input--with-icon-right.ds-input--small {
|
|
84
99
|
padding-right: calc(var(--height-s) / 4 + 20px + var(--height-s) / 4);
|
|
85
100
|
}
|
|
@@ -96,6 +111,10 @@
|
|
|
96
111
|
pointer-events: none;
|
|
97
112
|
}
|
|
98
113
|
|
|
114
|
+
.ds-input-icon--xs {
|
|
115
|
+
left: 6px;
|
|
116
|
+
}
|
|
117
|
+
|
|
99
118
|
.ds-input-icon--small {
|
|
100
119
|
left: 8px;
|
|
101
120
|
}
|
|
@@ -121,6 +140,10 @@
|
|
|
121
140
|
color: var(--color-txt-icon-1);
|
|
122
141
|
}
|
|
123
142
|
|
|
143
|
+
.ds-input-icon-right--xs {
|
|
144
|
+
right: 6px;
|
|
145
|
+
}
|
|
146
|
+
|
|
124
147
|
.ds-input-icon-right--small {
|
|
125
148
|
right: 8px;
|
|
126
149
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
.ds-radio {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: var(--space-s);
|
|
5
|
+
cursor: pointer;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.ds-radio--disabled {
|
|
9
|
+
opacity: 0.5;
|
|
10
|
+
cursor: not-allowed;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ds-radio__input {
|
|
14
|
+
position: absolute;
|
|
15
|
+
opacity: 0;
|
|
16
|
+
pointer-events: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ds-radio__circle {
|
|
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: 50%;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.ds-radio__input:checked + .ds-radio__circle {
|
|
32
|
+
background-color: var(--color-status-success);
|
|
33
|
+
border-color: var(--color-status-success);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ds-radio__input:not(:checked) + .ds-radio__circle:hover {
|
|
37
|
+
border-color: var(--color-border-hover);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.ds-radio__dot {
|
|
41
|
+
background-color: var(--color-white);
|
|
42
|
+
border-radius: 50%;
|
|
43
|
+
opacity: 0;
|
|
44
|
+
transition: opacity var(--transition-normal);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.ds-radio__input:checked + .ds-radio__circle .ds-radio__dot {
|
|
48
|
+
opacity: 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ds-radio__label {
|
|
52
|
+
font-size: var(--font-size-p1);
|
|
53
|
+
color: var(--color-txt-icon-1);
|
|
54
|
+
user-select: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Extra Small */
|
|
58
|
+
.ds-radio--xs .ds-radio__circle {
|
|
59
|
+
width: var(--icon-xs);
|
|
60
|
+
height: var(--icon-xs);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.ds-radio--xs .ds-radio__dot {
|
|
64
|
+
width: 6px;
|
|
65
|
+
height: 6px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ds-radio--xs .ds-radio__label {
|
|
69
|
+
font-size: var(--font-size-p3);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Small */
|
|
73
|
+
.ds-radio--small .ds-radio__circle {
|
|
74
|
+
width: var(--icon-s);
|
|
75
|
+
height: var(--icon-s);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.ds-radio--small .ds-radio__dot {
|
|
79
|
+
width: 8px;
|
|
80
|
+
height: 8px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ds-radio--small .ds-radio__label {
|
|
84
|
+
font-size: var(--font-size-p2);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Medium */
|
|
88
|
+
.ds-radio--medium .ds-radio__circle {
|
|
89
|
+
width: var(--icon-m);
|
|
90
|
+
height: var(--icon-m);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.ds-radio--medium .ds-radio__dot {
|
|
94
|
+
width: 10px;
|
|
95
|
+
height: 10px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.ds-radio--medium .ds-radio__label {
|
|
99
|
+
font-size: var(--font-size-p1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Large */
|
|
103
|
+
.ds-radio--large .ds-radio__circle {
|
|
104
|
+
width: var(--icon-l);
|
|
105
|
+
height: var(--icon-l);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.ds-radio--large .ds-radio__dot {
|
|
109
|
+
width: 12px;
|
|
110
|
+
height: 12px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.ds-radio--large .ds-radio__label {
|
|
114
|
+
font-size: var(--font-size-p1);
|
|
115
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './Radio.css';
|
|
3
|
+
|
|
4
|
+
export function Radio({
|
|
5
|
+
checked = false,
|
|
6
|
+
onChange,
|
|
7
|
+
size = 'medium',
|
|
8
|
+
disabled = false,
|
|
9
|
+
label,
|
|
10
|
+
name,
|
|
11
|
+
value,
|
|
12
|
+
id,
|
|
13
|
+
...props
|
|
14
|
+
}) {
|
|
15
|
+
const radioId = id || `radio-${React.useId()}`;
|
|
16
|
+
|
|
17
|
+
const className = [
|
|
18
|
+
'ds-radio',
|
|
19
|
+
`ds-radio--${size}`,
|
|
20
|
+
disabled && 'ds-radio--disabled',
|
|
21
|
+
].filter(Boolean).join(' ');
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<label className={className} htmlFor={radioId}>
|
|
25
|
+
<input
|
|
26
|
+
type="radio"
|
|
27
|
+
id={radioId}
|
|
28
|
+
className="ds-radio__input"
|
|
29
|
+
checked={checked}
|
|
30
|
+
onChange={(e) => onChange?.(e.target.checked)}
|
|
31
|
+
disabled={disabled}
|
|
32
|
+
name={name}
|
|
33
|
+
value={value}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
<span className="ds-radio__circle">
|
|
37
|
+
<span className="ds-radio__dot" />
|
|
38
|
+
</span>
|
|
39
|
+
{label && <span className="ds-radio__label">{label}</span>}
|
|
40
|
+
</label>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -60,6 +60,13 @@
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/* Sizes */
|
|
63
|
+
.ds-select-trigger--xs {
|
|
64
|
+
height: var(--height-xs);
|
|
65
|
+
padding: 0 calc(var(--height-xs) / 4);
|
|
66
|
+
font-size: var(--font-size-p3);
|
|
67
|
+
border-radius: var(--radius-xs);
|
|
68
|
+
}
|
|
69
|
+
|
|
63
70
|
.ds-select-trigger--small {
|
|
64
71
|
height: var(--height-s);
|
|
65
72
|
padding: 0 calc(var(--height-s) / 4);
|
|
@@ -53,7 +53,7 @@ export function Select({
|
|
|
53
53
|
}, [isOpen]);
|
|
54
54
|
|
|
55
55
|
const selectedOption = options.find(opt => opt.value === value);
|
|
56
|
-
const iconSize = size === 'small' ? iconSizes.s : iconSizes.m;
|
|
56
|
+
const iconSize = size === 'xs' ? iconSizes.xs : size === 'small' ? iconSizes.s : iconSizes.m;
|
|
57
57
|
|
|
58
58
|
return (
|
|
59
59
|
<div className={`ds-select-wrapper ${fullWidth ? 'ds-select-wrapper--full' : ''} ${disabled ? 'ds-select-wrapper--disabled' : ''}`}>
|
|
@@ -98,6 +98,22 @@
|
|
|
98
98
|
|
|
99
99
|
/* Sizes */
|
|
100
100
|
|
|
101
|
+
/* Extra Small */
|
|
102
|
+
.ds-stepper--xs {
|
|
103
|
+
height: var(--height-xs);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.ds-stepper--xs .ds-stepper-button {
|
|
107
|
+
width: var(--height-xs);
|
|
108
|
+
padding: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.ds-stepper--xs .ds-stepper-input {
|
|
112
|
+
font-size: var(--font-size-p3);
|
|
113
|
+
letter-spacing: var(--letter-spacing-p3);
|
|
114
|
+
padding: 0 var(--space-xs);
|
|
115
|
+
}
|
|
116
|
+
|
|
101
117
|
/* Small */
|
|
102
118
|
.ds-stepper--small {
|
|
103
119
|
height: var(--height-s);
|
|
@@ -57,7 +57,7 @@ export function Stepper({
|
|
|
57
57
|
disabled && 'ds-stepper--disabled',
|
|
58
58
|
].filter(Boolean).join(' ');
|
|
59
59
|
|
|
60
|
-
const iconSize = size === 'small' ? iconSizes.s : iconSizes.m;
|
|
60
|
+
const iconSize = size === 'xs' ? iconSizes.xs : size === 'small' ? iconSizes.s : iconSizes.m;
|
|
61
61
|
|
|
62
62
|
return (
|
|
63
63
|
<div className={className}>
|
package/src/components/Tabs.css
CHANGED
|
@@ -37,6 +37,13 @@
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/* Sizes */
|
|
40
|
+
.ds-tab--xs {
|
|
41
|
+
height: var(--height-xs);
|
|
42
|
+
padding: 0 calc(var(--height-xs) / 2);
|
|
43
|
+
font-size: var(--font-size-p3);
|
|
44
|
+
border-radius: var(--radius-xs);
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
.ds-tab--small {
|
|
41
48
|
height: var(--height-s);
|
|
42
49
|
padding: 0 calc(var(--height-s) / 2);
|
|
@@ -87,6 +87,12 @@
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/* Sizes */
|
|
90
|
+
.ds-textarea--xs {
|
|
91
|
+
padding: var(--space-xs);
|
|
92
|
+
font-size: var(--font-size-p3);
|
|
93
|
+
border-radius: var(--radius-xs);
|
|
94
|
+
}
|
|
95
|
+
|
|
90
96
|
.ds-textarea--small {
|
|
91
97
|
padding: var(--space-xs) var(--space-s);
|
|
92
98
|
font-size: var(--font-size-p2);
|
|
@@ -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(--
|
|
54
|
-
height: var(--
|
|
55
|
-
border-radius: calc(var(--
|
|
56
|
-
padding:
|
|
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(--
|
|
61
|
-
height: calc(var(--
|
|
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(--
|
|
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(--
|
|
75
|
-
height: var(--
|
|
76
|
-
border-radius: calc(var(--
|
|
77
|
-
padding:
|
|
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(--
|
|
82
|
-
height: calc(var(--
|
|
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(--
|
|
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={
|
|
91
|
-
onMouseLeave={
|
|
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,5 @@ 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';
|
|
24
|
+
export { Radio } from './components/Radio';
|