@donkit-ai/design-system 0.2.3

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.
@@ -0,0 +1,163 @@
1
+ /* Stepper Component */
2
+
3
+ .ds-stepper-wrapper {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--space-xs);
7
+ }
8
+
9
+ .ds-stepper-label {
10
+ font-size: var(--font-size-p2);
11
+ letter-spacing: var(--letter-spacing-p2);
12
+ color: var(--color-txt-icon-1);
13
+ }
14
+
15
+ .ds-stepper {
16
+ display: flex;
17
+ align-items: stretch;
18
+ width: fit-content;
19
+ border: 1px solid var(--color-border);
20
+ border-radius: var(--radius-xs);
21
+ transition: border-color var(--transition-normal);
22
+ }
23
+
24
+ .ds-stepper:hover:not(.ds-stepper--disabled) {
25
+ border-color: var(--color-border-hover);
26
+ }
27
+
28
+ .ds-stepper:focus-within {
29
+ border-color: var(--color-border-hover);
30
+ }
31
+
32
+ .ds-stepper--disabled {
33
+ cursor: not-allowed;
34
+ }
35
+
36
+ .ds-stepper-button {
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ background-color: transparent;
41
+ border: none;
42
+ color: var(--color-txt-icon-2);
43
+ cursor: pointer;
44
+ transition: background-color var(--transition-fast), color var(--transition-fast);
45
+ flex-shrink: 0;
46
+ }
47
+
48
+ .ds-stepper-button:hover:not(:disabled) {
49
+ background-color: var(--color-item-bg-hover);
50
+ color: var(--color-txt-icon-1);
51
+ }
52
+
53
+ .ds-stepper-button:active:not(:disabled) {
54
+ background-color: var(--color-item-bg-selected);
55
+ }
56
+
57
+ .ds-stepper-button:disabled {
58
+ opacity: 0.5;
59
+ cursor: not-allowed;
60
+ }
61
+
62
+ .ds-stepper-button--minus {
63
+ border-radius: var(--radius-xs) 0 0 var(--radius-xs);
64
+ border-right: 1px solid var(--color-border);
65
+ }
66
+
67
+ .ds-stepper-button--plus {
68
+ border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
69
+ border-left: 1px solid var(--color-border);
70
+ }
71
+
72
+ .ds-stepper-input {
73
+ width: 80px;
74
+ text-align: center;
75
+ border: none;
76
+ background-color: transparent;
77
+ color: var(--color-txt-icon-1);
78
+ font-size: var(--font-size-p1);
79
+ letter-spacing: var(--letter-spacing-p1);
80
+ outline: none;
81
+ cursor: text;
82
+ }
83
+
84
+ .ds-stepper-input:disabled {
85
+ cursor: not-allowed;
86
+ }
87
+
88
+ /* Remove spinner arrows */
89
+ .ds-stepper-input::-webkit-inner-spin-button,
90
+ .ds-stepper-input::-webkit-outer-spin-button {
91
+ -webkit-appearance: none;
92
+ margin: 0;
93
+ }
94
+
95
+ .ds-stepper-input[type=number] {
96
+ -moz-appearance: textfield;
97
+ }
98
+
99
+ /* Sizes */
100
+
101
+ /* Small */
102
+ .ds-stepper--small .ds-stepper-button {
103
+ width: 32px;
104
+ height: 32px;
105
+ padding: 0;
106
+ }
107
+
108
+ .ds-stepper--small .ds-stepper-input {
109
+ height: 32px;
110
+ font-size: var(--font-size-p2);
111
+ letter-spacing: var(--letter-spacing-p2);
112
+ padding: 0 var(--space-xs);
113
+ }
114
+
115
+ /* Medium */
116
+ .ds-stepper--medium .ds-stepper-button {
117
+ width: 40px;
118
+ height: 40px;
119
+ padding: 0;
120
+ }
121
+
122
+ .ds-stepper--medium .ds-stepper-input {
123
+ height: 40px;
124
+ font-size: var(--font-size-p1);
125
+ letter-spacing: var(--letter-spacing-p1);
126
+ padding: 0 var(--space-s);
127
+ }
128
+
129
+ /* Hint & Error */
130
+ .ds-stepper-hint {
131
+ font-size: var(--font-size-p2);
132
+ letter-spacing: var(--letter-spacing-p2);
133
+ color: var(--color-txt-icon-2);
134
+ }
135
+
136
+ .ds-stepper-error {
137
+ font-size: var(--font-size-p2);
138
+ letter-spacing: var(--letter-spacing-p2);
139
+ color: var(--color-status-error);
140
+ }
141
+
142
+ /* Error state */
143
+ .ds-stepper-wrapper--error .ds-stepper {
144
+ border-color: var(--color-status-error);
145
+ }
146
+
147
+ .ds-stepper-wrapper--error .ds-stepper:hover:not(.ds-stepper--disabled) {
148
+ border-color: var(--color-status-error);
149
+ }
150
+
151
+ .ds-stepper-wrapper--error .ds-stepper:focus-within {
152
+ border-color: var(--color-status-error);
153
+ }
154
+
155
+ /* Disabled state */
156
+ .ds-stepper-wrapper--disabled {
157
+ opacity: 0.5;
158
+ cursor: not-allowed;
159
+ }
160
+
161
+ .ds-stepper-wrapper--disabled * {
162
+ cursor: not-allowed;
163
+ }
@@ -0,0 +1,103 @@
1
+ import React from 'react';
2
+ import { Minus, Plus } from 'lucide-react';
3
+ import './Stepper.css';
4
+
5
+ export function Stepper({
6
+ label,
7
+ value = 0,
8
+ onChange,
9
+ min = 0,
10
+ max = 100,
11
+ step = 1,
12
+ size = 'medium',
13
+ disabled = false,
14
+ hint,
15
+ error,
16
+ ...props
17
+ }) {
18
+ const handleIncrement = () => {
19
+ if (disabled) return;
20
+ const newValue = Math.min(Number(value) + step, max);
21
+ onChange?.(newValue);
22
+ };
23
+
24
+ const handleDecrement = () => {
25
+ if (disabled) return;
26
+ const newValue = Math.max(Number(value) - step, min);
27
+ onChange?.(newValue);
28
+ };
29
+
30
+ const handleInputChange = (e) => {
31
+ if (disabled) return;
32
+ const newValue = e.target.value;
33
+
34
+ // Allow empty string for user to clear and type
35
+ if (newValue === '') {
36
+ onChange?.(min);
37
+ return;
38
+ }
39
+
40
+ const numValue = Number(newValue);
41
+ if (!isNaN(numValue)) {
42
+ const clampedValue = Math.min(Math.max(numValue, min), max);
43
+ onChange?.(clampedValue);
44
+ }
45
+ };
46
+
47
+ const className = [
48
+ 'ds-stepper-wrapper',
49
+ disabled && 'ds-stepper-wrapper--disabled',
50
+ error && 'ds-stepper-wrapper--error',
51
+ ].filter(Boolean).join(' ');
52
+
53
+ const stepperClassName = [
54
+ 'ds-stepper',
55
+ `ds-stepper--${size}`,
56
+ disabled && 'ds-stepper--disabled',
57
+ ].filter(Boolean).join(' ');
58
+
59
+ const iconSize = size === 'small' ? 16 : 20;
60
+
61
+ return (
62
+ <div className={className}>
63
+ {label && <label className="ds-stepper-label">{label}</label>}
64
+
65
+ <div className={stepperClassName}>
66
+ <button
67
+ type="button"
68
+ className="ds-stepper-button ds-stepper-button--minus"
69
+ onClick={handleDecrement}
70
+ disabled={disabled || value <= min}
71
+ aria-label="Decrease"
72
+ >
73
+ <Minus size={iconSize} strokeWidth={1.5} />
74
+ </button>
75
+
76
+ <input
77
+ type="number"
78
+ className="ds-stepper-input"
79
+ value={value}
80
+ onChange={handleInputChange}
81
+ min={min}
82
+ max={max}
83
+ step={step}
84
+ disabled={disabled}
85
+ {...props}
86
+ />
87
+
88
+ <button
89
+ type="button"
90
+ className="ds-stepper-button ds-stepper-button--plus"
91
+ onClick={handleIncrement}
92
+ disabled={disabled || value >= max}
93
+ aria-label="Increase"
94
+ >
95
+ <Plus size={iconSize} strokeWidth={1.5} />
96
+ </button>
97
+ </div>
98
+
99
+ {hint && !error && <div className="ds-stepper-hint">{hint}</div>}
100
+ {error && <div className="ds-stepper-error">{error}</div>}
101
+ </div>
102
+ );
103
+ }
@@ -0,0 +1,92 @@
1
+ .ds-tabs {
2
+ display: flex;
3
+ gap: var(--space-xs);
4
+ flex-wrap: wrap;
5
+ }
6
+
7
+ .ds-tab {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ gap: var(--space-xs);
12
+ font-family: inherit;
13
+ font-weight: 400;
14
+ line-height: 1;
15
+ cursor: pointer;
16
+ background-color: transparent;
17
+ color: var(--color-txt-icon-1);
18
+ transition: border-color var(--transition-normal), background-color var(--transition-normal);
19
+ white-space: nowrap;
20
+ }
21
+
22
+ /* Variants */
23
+ .ds-tab--secondary {
24
+ border: 1px solid var(--color-border);
25
+ }
26
+
27
+ .ds-tab--secondary:hover:not(.ds-tab--selected):not(:disabled) {
28
+ background-color: var(--color-item-bg-hover);
29
+ border-color: var(--color-border-hover);
30
+ }
31
+
32
+ .ds-tab--secondary.ds-tab--selected {
33
+ background-color: var(--color-item-bg-selected);
34
+ border-color: var(--color-border-selected);
35
+ }
36
+
37
+ .ds-tab--ghost {
38
+ border: none;
39
+ color: var(--color-txt-icon-2);
40
+ }
41
+
42
+ .ds-tab--ghost:hover:not(.ds-tab--selected):not(:disabled) {
43
+ background-color: var(--color-item-bg-hover);
44
+ color: var(--color-txt-icon-1);
45
+ }
46
+
47
+ .ds-tab--ghost.ds-tab--selected {
48
+ background-color: var(--color-item-bg-selected);
49
+ color: var(--color-txt-icon-1);
50
+ }
51
+
52
+ /* Sizes */
53
+ .ds-tab--small {
54
+ height: calc(20px + var(--space-xs) * 2);
55
+ padding: 0 var(--space-s);
56
+ font-size: var(--font-size-p2);
57
+ border-radius: var(--radius-xs);
58
+ }
59
+
60
+ .ds-tab--medium {
61
+ height: calc(24px + var(--space-s) * 2);
62
+ padding: 0 var(--space-s);
63
+ font-size: var(--font-size-p1);
64
+ border-radius: var(--radius-s);
65
+ }
66
+
67
+ .ds-tab--large {
68
+ height: calc(28px + var(--space-m) * 2);
69
+ padding: 0 var(--space-m);
70
+ font-size: var(--font-size-p1);
71
+ border-radius: var(--radius-s);
72
+ gap: var(--space-s);
73
+ }
74
+
75
+ .ds-tab:disabled {
76
+ opacity: 0.5;
77
+ cursor: not-allowed;
78
+ }
79
+
80
+ .ds-tab-icon {
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ flex-shrink: 0;
85
+ }
86
+
87
+ .ds-tab--icon-only {
88
+ aspect-ratio: 1;
89
+ justify-content: center;
90
+ padding-left: 0;
91
+ padding-right: 0;
92
+ }
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import './Tabs.css';
3
+
4
+ export function Tabs({ children, size = 'medium', variant = 'secondary', ...props }) {
5
+ return (
6
+ <div className="ds-tabs" role="tablist" {...props}>
7
+ {React.Children.map(children, (child) => {
8
+ if (React.isValidElement(child)) {
9
+ return React.cloneElement(child, { size, variant });
10
+ }
11
+ return child;
12
+ })}
13
+ </div>
14
+ );
15
+ }
16
+
17
+ export function Tab({ children, selected = false, onClick, size = 'medium', variant = 'secondary', disabled = false, icon, ...props }) {
18
+ const isIconOnly = icon && !children;
19
+
20
+ const className = [
21
+ 'ds-tab',
22
+ `ds-tab--${size}`,
23
+ `ds-tab--${variant}`,
24
+ selected && 'ds-tab--selected',
25
+ isIconOnly && 'ds-tab--icon-only',
26
+ ].filter(Boolean).join(' ');
27
+
28
+ return (
29
+ <button
30
+ role="tab"
31
+ aria-selected={selected}
32
+ className={className}
33
+ onClick={onClick}
34
+ disabled={disabled}
35
+ {...props}
36
+ >
37
+ {icon && <span className="ds-tab-icon">{icon}</span>}
38
+ {children}
39
+ </button>
40
+ );
41
+ }
@@ -0,0 +1,80 @@
1
+ .ds-textarea-wrapper {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--space-xs);
5
+ }
6
+
7
+ .ds-textarea-wrapper--full {
8
+ width: 100%;
9
+ }
10
+
11
+ .ds-textarea-wrapper--disabled {
12
+ opacity: 0.5;
13
+ cursor: not-allowed;
14
+ }
15
+
16
+ .ds-textarea-label {
17
+ font-size: var(--font-size-p2);
18
+ font-weight: 400;
19
+ color: var(--color-txt-icon-1);
20
+ }
21
+
22
+ .ds-textarea {
23
+ width: 100%;
24
+ font-family: inherit;
25
+ color: var(--color-txt-icon-1);
26
+ background-color: transparent;
27
+ border: 1px solid var(--color-border);
28
+ transition: border-color var(--transition-normal);
29
+ line-height: 1.5;
30
+ resize: vertical;
31
+ }
32
+
33
+ .ds-textarea::placeholder {
34
+ color: var(--color-txt-icon-2);
35
+ }
36
+
37
+ .ds-textarea:hover:not(:disabled) {
38
+ border-color: var(--color-border-hover);
39
+ }
40
+
41
+ .ds-textarea:focus,
42
+ .ds-textarea:active {
43
+ outline: none;
44
+ border-color: var(--color-border-hover);
45
+ }
46
+
47
+ .ds-textarea:disabled {
48
+ cursor: not-allowed;
49
+ }
50
+
51
+ .ds-textarea--error {
52
+ border-color: var(--color-error);
53
+ }
54
+
55
+ .ds-textarea--no-resize {
56
+ resize: none;
57
+ }
58
+
59
+ /* Sizes */
60
+ .ds-textarea--small {
61
+ padding: var(--space-xs) var(--space-s);
62
+ font-size: var(--font-size-p2);
63
+ border-radius: var(--radius-xs);
64
+ }
65
+
66
+ .ds-textarea--medium {
67
+ padding: var(--space-s);
68
+ font-size: var(--font-size-p1);
69
+ border-radius: var(--radius-s);
70
+ }
71
+
72
+ .ds-textarea-hint {
73
+ font-size: var(--font-size-p2);
74
+ color: var(--color-txt-icon-2);
75
+ }
76
+
77
+ .ds-textarea-error {
78
+ font-size: var(--font-size-p2);
79
+ color: var(--color-error);
80
+ }
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import './Textarea.css';
3
+
4
+ export function Textarea({
5
+ label,
6
+ error,
7
+ hint,
8
+ fullWidth = true,
9
+ size = 'medium',
10
+ disabled,
11
+ id,
12
+ resize = true,
13
+ rows = 3,
14
+ ...props
15
+ }) {
16
+ const textareaId = id || `textarea-${React.useId()}`;
17
+ const hintId = hint ? `${textareaId}-hint` : undefined;
18
+ const errorId = error ? `${textareaId}-error` : undefined;
19
+ const describedBy = errorId || hintId;
20
+
21
+ return (
22
+ <div className={`ds-textarea-wrapper ${fullWidth ? 'ds-textarea-wrapper--full' : ''} ${disabled ? 'ds-textarea-wrapper--disabled' : ''}`}>
23
+ {label && (
24
+ <label className="ds-textarea-label" htmlFor={textareaId}>
25
+ {label}
26
+ </label>
27
+ )}
28
+ <textarea
29
+ id={textareaId}
30
+ className={`ds-textarea ds-textarea--${size} ${error ? 'ds-textarea--error' : ''} ${!resize ? 'ds-textarea--no-resize' : ''}`}
31
+ disabled={disabled}
32
+ aria-invalid={error ? 'true' : 'false'}
33
+ aria-describedby={describedBy}
34
+ rows={rows}
35
+ {...props}
36
+ />
37
+ {hint && !error && <span id={hintId} className="ds-textarea-hint">{hint}</span>}
38
+ {error && <span id={errorId} className="ds-textarea-error" role="alert">{error}</span>}
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1,74 @@
1
+ .ds-h1 {
2
+ font-size: var(--font-size-h1);
3
+ font-weight: 400;
4
+ line-height: 1.2;
5
+ color: var(--color-txt-icon-1);
6
+ letter-spacing: var(--letter-spacing-h1);
7
+ margin: 0;
8
+ }
9
+
10
+ .ds-h2 {
11
+ font-size: var(--font-size-h2);
12
+ font-weight: 400;
13
+ line-height: 1.3;
14
+ color: var(--color-txt-icon-1);
15
+ letter-spacing: var(--letter-spacing-h2);
16
+ margin: 0;
17
+ }
18
+
19
+ .ds-h3 {
20
+ font-size: var(--font-size-h3);
21
+ font-weight: 400;
22
+ line-height: 1.3;
23
+ color: var(--color-txt-icon-1);
24
+ letter-spacing: var(--letter-spacing-h3);
25
+ margin: 0;
26
+ }
27
+
28
+ .ds-h4 {
29
+ font-size: var(--font-size-h4);
30
+ font-weight: 400;
31
+ line-height: 1.4;
32
+ color: var(--color-txt-icon-1);
33
+ letter-spacing: var(--letter-spacing-h4);
34
+ margin: 0;
35
+ }
36
+
37
+ .ds-p1 {
38
+ font-size: var(--font-size-p1);
39
+ font-weight: 400;
40
+ line-height: 1.5;
41
+ color: var(--color-txt-icon-1);
42
+ letter-spacing: var(--letter-spacing-p1);
43
+ margin: 0;
44
+ }
45
+
46
+ .ds-p1--secondary {
47
+ color: var(--color-txt-icon-2);
48
+ }
49
+
50
+ .ds-p2 {
51
+ font-size: var(--font-size-p2);
52
+ font-weight: 400;
53
+ line-height: 1.5;
54
+ color: var(--color-txt-icon-1);
55
+ letter-spacing: var(--letter-spacing-p2);
56
+ margin: 0;
57
+ }
58
+
59
+ .ds-p2--secondary {
60
+ color: var(--color-txt-icon-2);
61
+ }
62
+
63
+ .ds-p3 {
64
+ font-size: var(--font-size-p3);
65
+ font-weight: 400;
66
+ line-height: 1.5;
67
+ color: var(--color-txt-icon-1);
68
+ letter-spacing: var(--letter-spacing-p3);
69
+ margin: 0;
70
+ }
71
+
72
+ .ds-p3--secondary {
73
+ color: var(--color-txt-icon-2);
74
+ }
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import './Typography.css';
3
+
4
+ export function H1({ children, ...props }) {
5
+ return <h1 className="ds-h1" {...props}>{children}</h1>;
6
+ }
7
+
8
+ export function H2({ children, ...props }) {
9
+ return <h2 className="ds-h2" {...props}>{children}</h2>;
10
+ }
11
+
12
+ export function H3({ children, ...props }) {
13
+ return <h3 className="ds-h3" {...props}>{children}</h3>;
14
+ }
15
+
16
+ export function H4({ children, ...props }) {
17
+ return <h4 className="ds-h4" {...props}>{children}</h4>;
18
+ }
19
+
20
+ export function P1({ children, secondary = false, ...props }) {
21
+ return (
22
+ <p className={`ds-p1 ${secondary ? 'ds-p1--secondary' : ''}`} {...props}>
23
+ {children}
24
+ </p>
25
+ );
26
+ }
27
+
28
+ export function P2({ children, secondary = false, ...props }) {
29
+ return (
30
+ <p className={`ds-p2 ${secondary ? 'ds-p2--secondary' : ''}`} {...props}>
31
+ {children}
32
+ </p>
33
+ );
34
+ }
35
+
36
+ export function P3({ children, secondary = false, ...props }) {
37
+ return (
38
+ <p className={`ds-p3 ${secondary ? 'ds-p3--secondary' : ''}`} {...props}>
39
+ {children}
40
+ </p>
41
+ );
42
+ }
package/src/index.js ADDED
@@ -0,0 +1,19 @@
1
+ // Styles
2
+ export * from './styles/tokens.css';
3
+
4
+ // Components
5
+ export { Button } from './components/Button';
6
+ export { Input } from './components/Input';
7
+ export { Textarea } from './components/Textarea';
8
+ export { Select } from './components/Select';
9
+ export { Stepper } from './components/Stepper';
10
+ export { Card } from './components/Card';
11
+ export { H1, H2, H3, H4, P1, P2, P3 } from './components/Typography';
12
+ export { Badge } from './components/Badge';
13
+ export { Alert } from './components/Alert';
14
+ export { Modal, ModalFooter } from './components/Modal';
15
+ export { Code } from './components/Code';
16
+ export { Link } from './components/Link';
17
+ export { Tabs, Tab } from './components/Tabs';
18
+ export { Accordion } from './components/Accordion';
19
+ export { CodeAccordion } from './components/CodeAccordion';