@donkit-ai/design-system 0.2.18 → 0.3.0

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
@@ -37,19 +37,22 @@ 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 в трех размерах (small, medium, large). Поддерживает `href` для рендера как ссылка
42
- - **Input** - текстовые поля с поддержкой иконок, ошибок и подсказок (small, medium)
43
- - **Stepper** - числовое поле с кнопками +/- для изменения значения (small, medium)
44
- - **Toggle** - переключатель для включения/выключения опций (xs, small, medium, large)
45
- - **Checkbox** - чекбокс для выбора опций (xs, small, medium, large)
46
- - **Card** - карточки двух типов: info (информационная, прозрачный фон) и interactive (интерактивная с hover эффектом)
40
+ - **Button** - кнопки с вариантами (primary, secondary, ghost) и размерами (xs, s, m, l). Поддерживает `href` для рендера как ссылка
41
+ - **Tabs** - вкладки с состоянием selected в четырех размерах (xs, s, m, l). Поддерживает `href` для рендера как ссылка
42
+ - **Input** - текстовые поля с поддержкой иконок, ошибок и подсказок (xs, s, m)
43
+ - **Textarea** - многострочное текстовое поле (xs, s, m)
44
+ - **Select** - выпадающий список с кастомным дизайном (xs, s, m)
45
+ - **Stepper** - числовое поле с кнопками +/- для изменения значения (xs, s, m)
46
+ - **Toggle** - переключатель для включения/выключения опций (xs, s, m, l)
47
+ - **Checkbox** - чекбокс для выбора опций (xs, s, m, l)
48
+ - **Radio** - радиокнопка для выбора одного варианта из группы (xs, s, m, l)
49
+ - **Card** - карточки двух типов: info (информационная, прозрачный фон) и interactive (интерактивная с hover эффектом). Поддерживает `href` для рендера как ссылка
47
50
  - **Typography** - H1-H4, P1-P3 компоненты
48
51
  - **Code** - inline и block код с monospace шрифтом
49
52
  - **Link** - ссылки акцентным цветом, при hover цвет меняется и появляется подчеркивание
50
- - **Badge** - бейджи для статусов (success, error, warning, accent) в двух размерах (small, medium)
53
+ - **Badge** - бейджи для статусов (success, error, warning, accent) в двух размерах (s, m)
51
54
  - **Modal** - модальные окна с header и footer
52
- - **Select** - выпадающий список с кастомным дизайном (small, medium)
55
+ - **Select** - выпадающий список с кастомным дизайном (s, m)
53
56
 
54
57
  ## Использование в проекте
55
58
 
@@ -75,7 +78,7 @@ function MyComponent() {
75
78
  <P1 secondary>This is a demo</P1>
76
79
  <Button
77
80
  variant="primary"
78
- size="medium"
81
+ size="m"
79
82
  icon={<Mail size={24} strokeWidth={1.5} />}
80
83
  >
81
84
  Send Email
@@ -113,20 +116,25 @@ import { iconSizes } from '@donkit-ai/design-system';
113
116
  **Соответствие размеров компонентам:**
114
117
 
115
118
  - **16px (xs)** - очень мелкие элементы
119
+ - Extra Small кнопки (`size="xs"`)
120
+ - Extra Small табы (`size="xs"`)
121
+
116
122
  - **20px (s)** - компактные элементы
117
- - Small кнопки (`size="small"`)
118
- - Tabs (вкладки)
123
+ - Small кнопки (`size="s"`)
124
+ - Small табы (`size="s"`)
119
125
  - Modal (иконка закрытия)
120
126
  - Accordion, CodeAccordion
121
127
 
122
128
  - **24px (m)** - стандартные элементы
123
- - Medium кнопки (`size="medium"`, по умолчанию)
129
+ - Medium кнопки (`size="m"`, по умолчанию)
130
+ - Medium табы (`size="m"`)
124
131
  - Input (иконки в полях ввода)
125
132
  - Alert (иконки статусов)
126
133
  - Select (иконка выпадающего списка)
127
134
 
128
135
  - **28px (l)** - крупные элементы
129
- - Large кнопки (`size="large"`)
136
+ - Large кнопки (`size="l"`)
137
+ - Large табы (`size="l"`)
130
138
 
131
139
  - **48px (xl)** - очень крупные элементы
132
140
 
@@ -139,7 +147,7 @@ import { Mail, Search, Eye, EyeOff, AlertCircle, Check, X } from 'lucide-react';
139
147
  import { iconSizes } from '@donkit-ai/design-system';
140
148
 
141
149
  // Small button / Tabs / Modal
142
- <Button size="small" icon={<Mail size={iconSizes.s} strokeWidth={1.5} />}>
150
+ <Button size="s" icon={<Mail size={iconSizes.s} strokeWidth={1.5} />}>
143
151
  Send
144
152
  </Button>
145
153
  <Tab icon={<AlertCircle size={iconSizes.s} strokeWidth={1.5} />}>
@@ -147,7 +155,7 @@ import { iconSizes } from '@donkit-ai/design-system';
147
155
  </Tab>
148
156
 
149
157
  // Medium button / Input / Alert
150
- <Button size="medium" icon={<Search size={iconSizes.m} strokeWidth={1.5} />}>
158
+ <Button size="m" icon={<Search size={iconSizes.m} strokeWidth={1.5} />}>
151
159
  Search
152
160
  </Button>
153
161
  <Input
@@ -160,7 +168,7 @@ import { iconSizes } from '@donkit-ai/design-system';
160
168
  />
161
169
 
162
170
  // Large button
163
- <Button size="large" icon={<Mail size={iconSizes.l} strokeWidth={1.5} />}>
171
+ <Button size="l" icon={<Mail size={iconSizes.l} strokeWidth={1.5} />}>
164
172
  Send Email
165
173
  </Button>
166
174
 
@@ -334,20 +342,23 @@ document.documentElement.setAttribute('data-theme', 'light');
334
342
  <Button variant="ghost">Ghost</Button>
335
343
 
336
344
  // Размеры с соответствующими иконками
337
- <Button size="small" icon={<Mail size={20} strokeWidth={1.5} />}>
345
+ <Button size="xs" icon={<Mail size={16} strokeWidth={1.5} />}>
346
+ Extra Small
347
+ </Button>
348
+ <Button size="s" icon={<Mail size={20} strokeWidth={1.5} />}>
338
349
  Small
339
350
  </Button>
340
- <Button size="medium" icon={<Mail size={24} strokeWidth={1.5} />}>
351
+ <Button size="m" icon={<Mail size={24} strokeWidth={1.5} />}>
341
352
  Medium
342
353
  </Button>
343
- <Button size="large" icon={<Mail size={28} strokeWidth={1.5} />}>
354
+ <Button size="l" icon={<Mail size={28} strokeWidth={1.5} />}>
344
355
  Large
345
356
  </Button>
346
357
 
347
358
  // С fullWidth
348
359
  <Button
349
360
  variant="primary"
350
- size="medium"
361
+ size="m"
351
362
  icon={<Mail size={24} strokeWidth={1.5} />}
352
363
  fullWidth
353
364
  >
@@ -364,7 +375,7 @@ import { Tabs, Tab } from '@donkit-ai/design-system';
364
375
  import { AlertCircle } from 'lucide-react';
365
376
 
366
377
  // Basic tabs
367
- <Tabs size="medium">
378
+ <Tabs size="m">
368
379
  <Tab selected={selectedTab === 'tab1'} onClick={() => setSelectedTab('tab1')}>
369
380
  Overview
370
381
  </Tab>
@@ -376,8 +387,9 @@ import { AlertCircle } from 'lucide-react';
376
387
  </Tab>
377
388
  </Tabs>
378
389
 
379
- // With icons (всегда 20px для Tabs)
380
- <Tabs size="small">
390
+ // With icons (размер иконки соответствует размеру таба)
391
+ // xs: 16px, small: 20px, medium: 24px, large: 28px
392
+ <Tabs size="s">
381
393
  <Tab
382
394
  selected={selectedTab === 'alerts'}
383
395
  onClick={() => setSelectedTab('alerts')}
@@ -391,7 +403,7 @@ import { AlertCircle } from 'lucide-react';
391
403
  </Tabs>
392
404
 
393
405
  // Disabled tab
394
- <Tabs size="medium">
406
+ <Tabs size="m">
395
407
  <Tab selected>Active</Tab>
396
408
  <Tab disabled>Disabled</Tab>
397
409
  </Tabs>
@@ -399,7 +411,7 @@ import { AlertCircle } from 'lucide-react';
399
411
  // As links (с href) - рендерит <a> вместо <button>
400
412
  // При обычном клике: вызывается onClick (preventDefault автоматический)
401
413
  // При Cmd/Ctrl+Click или Middle Click: открывается новая вкладка
402
- <Tabs size="medium">
414
+ <Tabs size="m">
403
415
  <Tab selected href="/overview" onClick={() => navigate('/overview')}>
404
416
  Overview
405
417
  </Tab>
@@ -417,7 +429,7 @@ import { AlertCircle } from 'lucide-react';
417
429
  - Hover: фон `--color-item-bg-hover`, текст `--color-txt-icon-1`
418
430
  - Default: текст `--color-txt-icon-2`
419
431
 
420
- **Размеры:** small, medium, large
432
+ **Размеры:** xs, small, medium, large
421
433
 
422
434
  **Дополнительно:** поддержка иконок и disabled состояния
423
435
 
@@ -427,7 +439,7 @@ import { AlertCircle } from 'lucide-react';
427
439
  // Basic input
428
440
  <Input
429
441
  label="Email"
430
- size="medium"
442
+ size="m"
431
443
  type="email"
432
444
  placeholder="email@example.com"
433
445
  hint="We'll never share your email"
@@ -436,7 +448,7 @@ import { AlertCircle } from 'lucide-react';
436
448
  // Left icon (informational) - size small
437
449
  <Input
438
450
  label="Search"
439
- size="small"
451
+ size="s"
440
452
  icon={<Search size={20} strokeWidth={1.5} />}
441
453
  placeholder="Search..."
442
454
  />
@@ -444,7 +456,7 @@ import { AlertCircle } from 'lucide-react';
444
456
  // Left icon - size medium
445
457
  <Input
446
458
  label="Search"
447
- size="medium"
459
+ size="m"
448
460
  icon={<Search size={24} strokeWidth={1.5} />}
449
461
  placeholder="Search..."
450
462
  />
@@ -452,7 +464,7 @@ import { AlertCircle } from 'lucide-react';
452
464
  // Right icon (action with hover) - size medium
453
465
  <Input
454
466
  label="Password"
455
- size="medium"
467
+ size="m"
456
468
  type={showPassword ? 'text' : 'password'}
457
469
  iconRight={showPassword ? <EyeOff size={24} strokeWidth={1.5} /> : <Eye size={24} strokeWidth={1.5} />}
458
470
  onIconRightClick={() => setShowPassword(!showPassword)}
@@ -476,7 +488,7 @@ import { AlertCircle } from 'lucide-react';
476
488
  ```jsx
477
489
  <Select
478
490
  label="Choose option"
479
- size="medium"
491
+ size="m"
480
492
  value={value}
481
493
  onChange={setValue}
482
494
  options={[
@@ -495,7 +507,7 @@ import { AlertCircle } from 'lucide-react';
495
507
  // Basic stepper
496
508
  <Stepper
497
509
  label="Quantity"
498
- size="medium"
510
+ size="m"
499
511
  value={quantity}
500
512
  onChange={setQuantity}
501
513
  min={1}
@@ -506,7 +518,7 @@ import { AlertCircle } from 'lucide-react';
506
518
  // Small size
507
519
  <Stepper
508
520
  label="Price"
509
- size="small"
521
+ size="s"
510
522
  value={price}
511
523
  onChange={setPrice}
512
524
  min={0}
@@ -539,7 +551,7 @@ import { AlertCircle } from 'lucide-react';
539
551
  - `min` - минимальное значение (default: 0)
540
552
  - `max` - максимальное значение (default: 100)
541
553
  - `step` - шаг изменения (default: 1)
542
- - `size` - размер: "small" или "medium" (default: "medium")
554
+ - `size` - размер: "s" или "m" (default: "medium")
543
555
  - `label` - подпись поля
544
556
  - `hint` - подсказка под полем
545
557
  - `error` - текст ошибки (показывается вместо hint)
@@ -571,7 +583,7 @@ import { AlertCircle } from 'lucide-react';
571
583
 
572
584
  // Small size (20px)
573
585
  <Toggle
574
- size="small"
586
+ size="s"
575
587
  checked={isEnabled}
576
588
  onChange={setIsEnabled}
577
589
  label="Auto-save"
@@ -579,7 +591,7 @@ import { AlertCircle } from 'lucide-react';
579
591
 
580
592
  // Medium size (24px, default)
581
593
  <Toggle
582
- size="medium"
594
+ size="m"
583
595
  checked={darkMode}
584
596
  onChange={setDarkMode}
585
597
  label="Dark mode"
@@ -587,7 +599,7 @@ import { AlertCircle } from 'lucide-react';
587
599
 
588
600
  // Large size (28px)
589
601
  <Toggle
590
- size="large"
602
+ size="l"
591
603
  checked={isEnabled}
592
604
  onChange={setIsEnabled}
593
605
  label="Enable feature"
@@ -651,7 +663,7 @@ import { AlertCircle } from 'lucide-react';
651
663
 
652
664
  // Small size (20px)
653
665
  <Checkbox
654
- size="small"
666
+ size="s"
655
667
  checked={isEnabled}
656
668
  onChange={setIsEnabled}
657
669
  label="Enable feature"
@@ -659,7 +671,7 @@ import { AlertCircle } from 'lucide-react';
659
671
 
660
672
  // Medium size (24px, default)
661
673
  <Checkbox
662
- size="medium"
674
+ size="m"
663
675
  checked={isSubscribed}
664
676
  onChange={setIsSubscribed}
665
677
  label="Subscribe to newsletter"
@@ -667,7 +679,7 @@ import { AlertCircle } from 'lucide-react';
667
679
 
668
680
  // Large size (28px)
669
681
  <Checkbox
670
- size="large"
682
+ size="l"
671
683
  checked={isAccepted}
672
684
  onChange={setIsAccepted}
673
685
  label="I accept the terms"
@@ -709,6 +721,129 @@ import { AlertCircle } from 'lucide-react';
709
721
  - Невыбранное состояние: фон `--color-item-bg`, border `--color-border`, hover `--color-border-hover`
710
722
  - Иконка галочки (Check) белого цвета при checked
711
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="s"
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="m"
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="l"
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
+
712
847
  ### Card
713
848
 
714
849
  Карточки бывают двух типов:
@@ -734,11 +869,45 @@ import { AlertCircle } from 'lucide-react';
734
869
  <Card padding="small">Small padding</Card>
735
870
  <Card padding="medium">Medium padding</Card>
736
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>
737
905
  ```
738
906
 
739
907
  **Стиль:**
740
908
  - **variant="info"** (по умолчанию): прозрачный фон, border `--color-border`
741
- - **variant="interactive"**: фон `--color-item-bg`, при hover фон `--color-item-bg-hover` и border `--color-border-hover`, курсор pointer
909
+ - **variant="interactive"**: фон `--color-item-bg`, при hover фон `--color-item-bg-hover`, курсор pointer
910
+ - Поддержка `href` для рендера как ссылка с умной обработкой кликов
742
911
 
743
912
  ### Code
744
913
 
@@ -768,15 +937,15 @@ import { AlertCircle } from 'lucide-react';
768
937
 
769
938
  ```jsx
770
939
  // Medium size (default)
771
- <Badge variant="success" size="medium">Success</Badge>
772
- <Badge variant="error" size="medium">Error</Badge>
773
- <Badge variant="warning" size="medium">Warning</Badge>
774
- <Badge variant="info" size="medium">Info</Badge>
940
+ <Badge variant="success" size="m">Success</Badge>
941
+ <Badge variant="error" size="m">Error</Badge>
942
+ <Badge variant="warning" size="m">Warning</Badge>
943
+ <Badge variant="info" size="m">Info</Badge>
775
944
 
776
945
  // Small size
777
- <Badge variant="success" size="small">Success</Badge>
778
- <Badge variant="error" size="small">Error</Badge>
779
- <Badge variant="warning" size="small">Warning</Badge>
946
+ <Badge variant="success" size="s">Success</Badge>
947
+ <Badge variant="error" size="s">Error</Badge>
948
+ <Badge variant="warning" size="s">Warning</Badge>
780
949
 
781
950
  // Variants
782
951
  <Badge variant="default">Default</Badge>
@@ -803,16 +972,16 @@ import { AlertCircle } from 'lucide-react';
803
972
  <Modal
804
973
  title="Modal Title"
805
974
  onClose={handleClose}
806
- size="medium"
975
+ size="m"
807
976
  >
808
977
  <P1>Modal content here...</P1>
809
978
  <Input label="Name" placeholder="Enter name" />
810
979
 
811
980
  <ModalFooter>
812
- <Button variant="secondary" size="medium" onClick={handleClose}>
981
+ <Button variant="secondary" size="m" onClick={handleClose}>
813
982
  Cancel
814
983
  </Button>
815
- <Button variant="primary" size="medium">
984
+ <Button variant="primary" size="m">
816
985
  Submit
817
986
  </Button>
818
987
  </ModalFooter>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkit-ai/design-system",
3
- "version": "0.2.18",
3
+ "version": "0.3.0",
4
4
  "description": "Donkit Design System - minimal design tokens and React components",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -7,14 +7,14 @@
7
7
  }
8
8
 
9
9
  /* Sizes */
10
- .ds-badge--small {
10
+ .ds-badge--s {
11
11
  padding: 0 4px;
12
12
  font-size: var(--font-size-p3);
13
13
  letter-spacing: var(--letter-spacing-p3);
14
14
  border-radius: var(--radius-xs);
15
15
  }
16
16
 
17
- .ds-badge--medium {
17
+ .ds-badge--m {
18
18
  padding: 2px var(--space-xs);
19
19
  font-size: var(--font-size-p2);
20
20
  letter-spacing: var(--letter-spacing-p2);
@@ -4,7 +4,7 @@ import './Badge.css';
4
4
  export function Badge({
5
5
  children,
6
6
  variant = 'default',
7
- size = 'medium',
7
+ size = 'm',
8
8
  role,
9
9
  ...props
10
10
  }) {
@@ -61,21 +61,28 @@
61
61
  }
62
62
 
63
63
  /* Sizes */
64
- .ds-button--small {
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
+
71
+ .ds-button--s {
65
72
  height: var(--height-s);
66
73
  padding: 0 calc(var(--height-s) / 2);
67
74
  font-size: var(--font-size-p2);
68
75
  border-radius: var(--radius-xs);
69
76
  }
70
77
 
71
- .ds-button--medium {
78
+ .ds-button--m {
72
79
  height: var(--height-m);
73
80
  padding: 0 calc(var(--height-m) / 2);
74
81
  font-size: var(--font-size-p1);
75
82
  border-radius: var(--radius-s);
76
83
  }
77
84
 
78
- .ds-button--large {
85
+ .ds-button--l {
79
86
  height: var(--height-l);
80
87
  padding: 0 calc(var(--height-l) / 2);
81
88
  font-size: var(--font-size-h4);
@@ -4,7 +4,7 @@ import './Button.css';
4
4
  export function Button({
5
5
  children,
6
6
  variant = 'primary',
7
- size = 'medium',
7
+ size = 'm',
8
8
  fullWidth = false,
9
9
  icon,
10
10
  disabled = false,
@@ -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,22 +13,28 @@
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
  }
21
29
 
22
- .ds-card--small {
30
+ .ds-card--s {
23
31
  padding: var(--space-s);
24
32
  }
25
33
 
26
- .ds-card--medium {
34
+ .ds-card--m {
27
35
  padding: var(--space-m);
28
36
  }
29
37
 
30
- .ds-card--large {
38
+ .ds-card--l {
31
39
  padding: var(--space-l);
32
40
  }
@@ -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 (