@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 +221 -52
- package/package.json +1 -1
- package/src/components/Badge.css +2 -2
- package/src/components/Badge.jsx +1 -1
- package/src/components/Button.css +10 -3
- package/src/components/Button.jsx +1 -1
- package/src/components/Card.css +12 -4
- package/src/components/Card.jsx +34 -1
- package/src/components/Checkbox.css +6 -6
- package/src/components/Checkbox.jsx +1 -1
- package/src/components/Input.css +33 -10
- package/src/components/Input.jsx +1 -1
- package/src/components/Modal.css +3 -3
- package/src/components/Modal.jsx +1 -1
- package/src/components/Radio.css +115 -0
- package/src/components/Radio.jsx +42 -0
- package/src/components/Select.css +13 -6
- package/src/components/Select.jsx +2 -2
- package/src/components/Stepper.css +22 -6
- package/src/components/Stepper.jsx +2 -2
- package/src/components/Tabs.css +10 -3
- package/src/components/Tabs.jsx +2 -2
- package/src/components/Textarea.css +8 -2
- package/src/components/Textarea.jsx +1 -1
- package/src/components/Toggle.css +12 -12
- package/src/components/Toggle.jsx +1 -1
- package/src/index.js +1 -0
- package/src/styles/tokens.css +1 -0
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) и размерами (
|
|
41
|
-
- **Tabs** - вкладки с состоянием selected в
|
|
42
|
-
- **Input** - текстовые поля с поддержкой иконок, ошибок и подсказок (
|
|
43
|
-
- **
|
|
44
|
-
- **
|
|
45
|
-
- **
|
|
46
|
-
- **
|
|
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) в двух размерах (
|
|
53
|
+
- **Badge** - бейджи для статусов (success, error, warning, accent) в двух размерах (s, m)
|
|
51
54
|
- **Modal** - модальные окна с header и footer
|
|
52
|
-
- **Select** - выпадающий список с кастомным дизайном (
|
|
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="
|
|
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="
|
|
118
|
-
-
|
|
123
|
+
- Small кнопки (`size="s"`)
|
|
124
|
+
- Small табы (`size="s"`)
|
|
119
125
|
- Modal (иконка закрытия)
|
|
120
126
|
- Accordion, CodeAccordion
|
|
121
127
|
|
|
122
128
|
- **24px (m)** - стандартные элементы
|
|
123
|
-
- Medium кнопки (`size="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
351
|
+
<Button size="m" icon={<Mail size={24} strokeWidth={1.5} />}>
|
|
341
352
|
Medium
|
|
342
353
|
</Button>
|
|
343
|
-
<Button size="
|
|
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="
|
|
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="
|
|
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 (
|
|
380
|
-
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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` - размер: "
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
|
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="
|
|
772
|
-
<Badge variant="error" size="
|
|
773
|
-
<Badge variant="warning" size="
|
|
774
|
-
<Badge variant="info" size="
|
|
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="
|
|
778
|
-
<Badge variant="error" size="
|
|
779
|
-
<Badge variant="warning" size="
|
|
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="
|
|
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="
|
|
981
|
+
<Button variant="secondary" size="m" onClick={handleClose}>
|
|
813
982
|
Cancel
|
|
814
983
|
</Button>
|
|
815
|
-
<Button variant="primary" size="
|
|
984
|
+
<Button variant="primary" size="m">
|
|
816
985
|
Submit
|
|
817
986
|
</Button>
|
|
818
987
|
</ModalFooter>
|
package/package.json
CHANGED
package/src/components/Badge.css
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/* Sizes */
|
|
10
|
-
.ds-badge--
|
|
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--
|
|
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);
|
package/src/components/Badge.jsx
CHANGED
|
@@ -61,21 +61,28 @@
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/* Sizes */
|
|
64
|
-
.ds-button--
|
|
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--
|
|
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--
|
|
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);
|
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,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--
|
|
30
|
+
.ds-card--s {
|
|
23
31
|
padding: var(--space-s);
|
|
24
32
|
}
|
|
25
33
|
|
|
26
|
-
.ds-card--
|
|
34
|
+
.ds-card--m {
|
|
27
35
|
padding: var(--space-m);
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
.ds-card--
|
|
38
|
+
.ds-card--l {
|
|
31
39
|
padding: var(--space-l);
|
|
32
40
|
}
|
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 (
|