@peassoft/mnr-web-ui-kit 2.4.0 → 3.0.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
@@ -1,7 +1,31 @@
1
1
  # Memorize'n'Revise Web UI Kit
2
2
 
3
- ## Light/Dark Color Schemes
3
+ UI kit and Design system for Memorize'n'Revise.
4
4
 
5
- The Kit supports the `prefers-color-scheme` media query.
5
+ **CAUTION!** You definitely should not use this package unless you're working on Memorize'n'Revise project.
6
6
 
7
- To check appearance of a component in any scheme, while running the Storybook, use Chrome DevTools to emulate a color scheme (`Rendering` tab).
7
+ ## Installation
8
+
9
+ ```shell
10
+ $ npm i @peassoft/mnr-web-ui-kit
11
+ ```
12
+
13
+ ## Light/Dark Color Schemes Support
14
+
15
+ The Design system utilizes CSS `light-dark()` function to support light and dark OS modes.
16
+
17
+ The `@peassoft/mnr-web-ui-kit/styles.css` file which you must import into your project already has necessary settings for `light-dark()` support:
18
+
19
+ ```css
20
+ :root,:host {
21
+ color-scheme: light dark;
22
+ }
23
+ ```
24
+
25
+ ## Usage Example
26
+
27
+ ```js
28
+ // Root of your project
29
+
30
+ import '@peassoft/mnr-web-ui-kit/styles.css';
31
+ ```
@@ -28,7 +28,7 @@ export function Button(props) {
28
28
  children: [_jsx("span", {
29
29
  className: labelCn,
30
30
  children: label
31
- }), !!isInProgress && _jsx("div", {
31
+ }), isInProgress && _jsx("div", {
32
32
  className: 'uikit_Button_spinner',
33
33
  "data-test-id": 'spinner'
34
34
  })]
@@ -1,32 +1,23 @@
1
1
  .uikit_Button_btn {
2
- --uikit-color-main: #3f72af;
3
- --uikit-color-secondary: #dbe2ef;
4
-
5
2
  display: block;
6
3
  appearance: none;
7
- color: var(--uikit-color-secondary);
8
- background-color: var(--uikit-color-main);
9
- border: 1px solid var(--uikit-color-main);
10
- border-radius: 5px;
4
+ color: var(--mnr-color-gray-1);
5
+ background-color: var(--mnr-color-accent);
6
+ border: 1px solid var(--mnr-color-accent);
7
+ border-radius: var(--mnr-radius-md);
11
8
  font-family: inherit;
12
- font-size: 1em;
9
+ font-size: var(--mnr-font-size-md);
13
10
  margin: 0;
14
- padding: 1em 2em;
11
+ padding: var(--mnr-spacing-md) var(--mnr-spacing-xl);
15
12
  cursor: pointer;
16
13
  position: relative;
17
- outline-offset: 1px;
14
+ outline-offset: var(--mnr-spacing-xs);
18
15
  }
19
16
 
20
17
  .uikit_Button_btn--secondary {
21
- color: var(--uikit-color-main);
22
- background-color: var(--uikit-color-secondary);
23
- border: 1px solid var(--uikit-color-main);
24
- }
25
-
26
- @media (prefers-color-scheme: dark) {
27
- .uikit_Button_btn--secondary {
28
- border-color: var(--uikit-color-secondary);
29
- }
18
+ color: var(--mnr-color-accent);
19
+ background-color: transparent;
20
+ border: 1px solid var(--mnr-color-accent);
30
21
  }
31
22
 
32
23
  .uikit_Button_btn--stretched {
@@ -34,7 +25,7 @@
34
25
  }
35
26
 
36
27
  .uikit_Button_btn--small {
37
- padding: .8em 1.4em;
28
+ padding: var(--mnr-spacing-sm) var(--mnr-spacing-lg);
38
29
  }
39
30
 
40
31
  .uikit_Button_label--hidden {
@@ -1,18 +1,9 @@
1
1
  .uikit_ButtonRadioGroup_label {
2
- margin: 0 0 0.5em;
3
- color: #666;
4
- }
5
-
6
- @media (prefers-color-scheme: dark) {
7
- .uikit_ButtonRadioGroup_label {
8
- color: #979797;
9
- }
2
+ margin: 0 0 var(--mnr-spacing-sm);
3
+ color: var(--mnr-color-text-on-page-background-secondary);
10
4
  }
11
5
 
12
6
  .uikit_ButtonRadioGroup_cont {
13
- --uikit-color-main: #3f72af;
14
- --uikit-color-secondary: #dbe2ef;
15
-
16
7
  display: flex;
17
8
  align-items: center;
18
9
  }
@@ -20,33 +11,27 @@
20
11
  .uikit_ButtonRadioGroup_btn {
21
12
  display: block;
22
13
  appearance: none;
23
- border: 1px solid var(--uikit-color-main);
14
+ border: 1px solid var(--mnr-color-accent);
24
15
  border-right: 0;
25
- color: var(--uikit-color-main);
26
- background-color: var(--uikit-color-secondary);
27
- border-radius: 5px 0 0 5px;
16
+ color: var(--mnr-color-accent);
17
+ background-color: transparent;
18
+ border-radius: var(--mnr-radius-md) 0 0 var(--mnr-radius-md);
28
19
  font-family: inherit;
29
- font-size: 1em;
20
+ font-size: var(--mnr-font-size-md);
30
21
  margin: 0;
31
- padding: 0.6em;
22
+ padding: var(--mnr-spacing-sm);
32
23
  cursor: pointer;
33
- outline-offset: 1px;
24
+ outline-offset: var(--mnr-spacing-xs);
34
25
  }
35
26
 
36
27
  .uikit_ButtonRadioGroup_btn--checked {
37
- color: var(--uikit-color-secondary);
38
- background-color: var(--uikit-color-main);
39
- }
40
-
41
- @media (prefers-color-scheme: dark) {
42
- .uikit_ButtonRadioGroup_btn--checked {
43
- border-color: var(--uikit-color-main);
44
- }
28
+ color: var(--mnr-color-gray-1);
29
+ background-color: var(--mnr-color-accent);
45
30
  }
46
31
 
47
32
  .uikit_ButtonRadioGroup_btn--last {
48
- border-radius: 0 5px 5px 0;
49
- border-right: 1px solid var(--uikit-color-main);
33
+ border-radius: 0 var(--mnr-radius-md) var(--mnr-radius-md) 0;
34
+ border-right: 1px solid var(--mnr-color-accent);
50
35
  }
51
36
 
52
37
  .uikit_ButtonRadioGroup_btn--mid {
@@ -1,4 +1,4 @@
1
- import { type JSX } from 'react';
1
+ import type { JSX } from 'react';
2
2
  import './styles.css';
3
3
  type Props = {
4
4
  /**
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import 'react';
3
2
  import './styles.css';
4
3
  export function ErrorMessage(props) {
5
4
  const {
@@ -1,15 +1,9 @@
1
1
  .uikit_ErrorMessage {
2
- font-size: 0.85em;
3
- color: #a7241b;
4
- margin: 0.5em 0 0;
5
- }
6
-
7
- @media (prefers-color-scheme: dark) {
8
- .uikit_ErrorMessage {
9
- color: #fb827a;
10
- }
2
+ font-size: var(--mnr-font-size-sm);
3
+ color: var(--mnr-color-error);
4
+ margin: var(--mnr-spacing-sm) 0 0;
11
5
  }
12
6
 
13
7
  .uikit_ErrorMessage--standalone {
14
- margin: 1.8em 0 0;
8
+ margin: var(--mnr-spacing-xl) 0 0;
15
9
  }
@@ -82,7 +82,7 @@ export function InputField(props) {
82
82
  onKeyDown: handleKeyDown,
83
83
  onBlur: onBlur || undefined
84
84
  })]
85
- }), type === 'password' && !!isCapslockOn && _jsxs("div", {
85
+ }), type === 'password' && isCapslockOn && _jsxs("div", {
86
86
  className: 'uikit_InputField_capslockMsg',
87
87
  children: [_jsx("svg", {
88
88
  width: '24',
@@ -1,5 +1,5 @@
1
1
  .uikit_InputField_cont:not(:first-child) {
2
- margin-top: 1.5em;
2
+ margin-block-start: var(--mnr-spacing-lg);
3
3
  }
4
4
 
5
5
  .uikit_InputField_innerCont {
@@ -10,104 +10,61 @@
10
10
 
11
11
  .uikit_InputField_label {
12
12
  display: block;
13
- margin-bottom: 0.25em;
14
- color: #737373;
13
+ margin-block-end: var(--mnr-spacing-xs);
14
+ color: var(--mnr-color-text-on-page-background-secondary);
15
15
  width: 100%;
16
16
  }
17
17
 
18
- @media (prefers-color-scheme: dark) {
19
- .uikit_InputField_label {
20
- color: #acacac;
21
- }
22
- }
23
-
24
18
  .uikit_InputField_required {
25
- color: #a7241b;
26
- }
27
-
28
- @media (prefers-color-scheme: dark) {
29
- .uikit_InputField_required {
30
- color: #fb827a;
31
- }
19
+ color: var(--mnr-color-error);
32
20
  }
33
21
 
34
22
  .uikit_InputField_input {
35
23
  display: block;
36
24
  box-sizing: border-box;
37
25
  width: 100%;
38
- border: 1px solid #bcbcbc;
39
- border-radius: 5px;
40
- font-size: 1.2em;
41
- padding: 0.5em 1em;
42
- color: #242629;
43
- background-color: #fff;
26
+ border: 1px solid var(--mnr-color-input-border);
27
+ border-radius: var(--mnr-radius-md);
28
+ font-size: var(--mnr-font-size-lg);
29
+ padding: var(--mnr-spacing-sm) var(--mnr-spacing-md);
30
+ color: var(--mnr-color-input-text);
31
+ background-color: var(--mnr-color-input-background);
44
32
  font-family: inherit;
45
33
  }
46
34
 
47
- @media (prefers-color-scheme: dark) {
48
- .uikit_InputField_input {
49
- background-color: #f9f9f9;
50
- border-color: #f9f9f9;
51
- }
52
- }
53
-
54
35
  .uikit_InputField_input:disabled {
55
- color: #848484;
56
- background-color: #efefef;
57
- }
58
-
59
- @media (prefers-color-scheme: dark) {
60
- .uikit_InputField_input:disabled {
61
- background-color: #dadada;
62
- border-color: #dadada;
63
- }
36
+ color: light-dark(
37
+ var(--mnr-color-gray-6),
38
+ var(--mnr-color-dark-2)
39
+ );
40
+ background-color: light-dark(
41
+ var(--mnr-color-gray-1),
42
+ var(--mnr-color-dark-4)
43
+ );
64
44
  }
65
45
 
66
46
  .uikit_InputField_capslockMsg {
67
47
  display: flex;
68
- gap: .5em;
48
+ gap: var(--mnr-spacing-sm);
69
49
  align-items: center;
70
- margin-top: 0.2em;
71
- font-size: 0.85rem;
72
- }
73
-
74
- @media (prefers-color-scheme: dark) {
75
- .uikit_InputField_capslockMsg {
76
- color: #acacac;
77
- }
50
+ margin-top: var(--mnr-spacing-xs);
51
+ font-size: var(--mnr-font-size-sm);
52
+ color: var(--mnr-color-text-on-page-background-secondary);
78
53
  }
79
54
 
80
55
  .uikit_InputField_capslockIcon {
81
- color: #a7241b;
56
+ color: var(--mnr-color-error);
82
57
  fill: currentColor;
83
58
  }
84
59
 
85
- @media (prefers-color-scheme: dark) {
86
- .uikit_InputField_capslockIcon {
87
- color: #fb827a;
88
- }
89
- }
90
-
91
60
  .uikit_InputField_errorMsg {
92
- color: #a7241b;
93
- font-size: 0.85rem;
94
- margin-top: 0.2em;
95
- }
96
-
97
- @media (prefers-color-scheme: dark) {
98
- .uikit_InputField_errorMsg {
99
- color: #fb827a;
100
- }
61
+ color: var(--mnr-color-error);
62
+ font-size: var(--mnr-font-size-sm);
63
+ margin-top: var(--mnr-spacing-xs);
101
64
  }
102
65
 
103
66
  .uikit_InputField_description {
104
- color: #737373;
105
- font-size: 0.85rem;
106
- margin-top: 0.2em;
107
- }
108
-
109
- @media (prefers-color-scheme: dark) {
110
- .uikit_InputField_description {
111
- color: #acacac;
112
- }
67
+ color: var(--mnr-color-text-on-page-background-secondary);
68
+ font-size: var(--mnr-font-size-sm);
69
+ margin-top: var(--mnr-spacing-xs);
113
70
  }
@@ -1,33 +1,24 @@
1
1
  .uikit_LinkButton {
2
- --uikit-color-main: #3f72af;
3
- --uikit-color-secondary: #dbe2ef;
4
-
5
2
  display: block;
6
3
  width: fit-content;
7
- color: var(--uikit-color-secondary);
8
- background-color: var(--uikit-color-main);
9
- border: 1px solid var(--uikit-color-main);
10
- border-radius: 5px;
4
+ color: var(--mnr-color-gray-1);
5
+ background-color: var(--mnr-color-accent);
6
+ border: 1px solid var(--mnr-color-accent);
7
+ border-radius: var(--mnr-radius-md);
11
8
  font-family: inherit;
12
- font-size: 1em;
13
- padding: 1em 2em;
9
+ font-size: var(--mnr-font-size-md);
10
+ padding: var(--mnr-spacing-md) var(--mnr-spacing-xl);
14
11
  cursor: pointer;
15
- outline-offset: 1px;
12
+ outline-offset: var(--mnr-spacing-xs);
16
13
  text-decoration: none;
17
14
  }
18
15
 
19
16
  .uikit_LinkButton--secondary {
20
- color: var(--uikit-color-main);
21
- background-color: var(--uikit-color-secondary);
22
- border: 1px solid var(--uikit-color-main);
23
- }
24
-
25
- @media (prefers-color-scheme: dark) {
26
- .uikit_LinkButton--secondary {
27
- border-color: var(--uikit-color-secondary);
28
- }
17
+ color: var(--mnr-color-accent);
18
+ background-color: transparent;
19
+ border: 1px solid var(--mnr-color-accent);
29
20
  }
30
21
 
31
22
  .uikit_LinkButton--small {
32
- padding: .8em 1.4em;
23
+ padding: var(--mnr-spacing-sm) var(--mnr-spacing-lg);
33
24
  }
@@ -26,18 +26,14 @@ export function PlainSelect(props) {
26
26
  const idx = items.findIndex(item => item.id === firstId);
27
27
  if (idx >= 0) {
28
28
  return idx;
29
- } else {
30
- return 0;
31
29
  }
32
- } else {
33
30
  return 0;
34
31
  }
35
- } else {
36
32
  return 0;
37
33
  }
38
- } else {
39
34
  return 0;
40
35
  }
36
+ return 0;
41
37
  });
42
38
  const itemRefs = useMemo(() => items.map(() => createRef()), [items]);
43
39
  const handleSingleSelectKeyDown = useCallback(event => {
@@ -156,12 +152,11 @@ export function PlainSelect(props) {
156
152
  children: label
157
153
  }), _jsx("ul", {
158
154
  className: 'uikit_PlainSelect_cont',
159
- style: {
160
- ...(maxHeight ? {
161
- maxHeight,
162
- overflow: 'auto'
163
- } : {})
164
- },
155
+ style: maxHeight ? {
156
+ maxHeight,
157
+ overflow: 'auto'
158
+ } : {},
159
+ /* oxlint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role */
165
160
  role: 'listbox',
166
161
  "aria-labelledby": labelDomId,
167
162
  "aria-multiselectable": multiple,
@@ -172,6 +167,8 @@ export function PlainSelect(props) {
172
167
  return _jsxs("li", {
173
168
  ref: itemRefs[idx],
174
169
  className: 'uikit_PlainSelect_itemCont',
170
+ /* oxlint-disable-next-line
171
+ jsx-a11y/no-noninteractive-element-to-interactive-role */
175
172
  role: 'option',
176
173
  "aria-selected": isSelected,
177
174
  tabIndex: multiple && currFocusedIdx === idx ? 0 : -1,
@@ -1,35 +1,22 @@
1
1
  .uikit_PlainSelect_label {
2
2
  margin: 0;
3
- color: #737373;
4
- }
5
-
6
- @media (prefers-color-scheme: dark) {
7
- .uikit_PlainSelect_label {
8
- color: #acacac;
9
- }
3
+ color: var(--mnr-color-text-on-page-background-secondary);
10
4
  }
11
5
 
12
6
  .uikit_PlainSelect_cont {
13
- margin: 0.5em 0 0;
14
- padding: 0.5em;
15
- background-color: #fff;
16
- border: 1px solid #bcbcbc;
7
+ margin: var(--mnr-spacing-sm) 0 0;
8
+ padding: var(--mnr-spacing-sm);
9
+ background-color: var(--mnr-color-input-background);
10
+ border: 1px solid var(--mnr-color-input-border);
17
11
  list-style-type: none;
18
- border-radius: 0.25em;
19
- }
20
-
21
- @media (prefers-color-scheme: dark) {
22
- .uikit_PlainSelect_cont {
23
- background-color: #f9f9f9;
24
- border-color: #f9f9f9;
25
- }
12
+ border-radius: var(--mnr-radius-sm);
26
13
  }
27
14
 
28
15
  .uikit_PlainSelect_itemCont {
29
16
  display: flex;
30
17
  align-items: flex-start;
31
- gap: 0.5rem;
32
- padding: 0.25em;
18
+ gap: var(--mnr-spacing-sm);
19
+ padding: var(--mnr-spacing-xs);
33
20
  cursor: pointer;
34
21
  }
35
22
 
@@ -38,23 +25,25 @@
38
25
  }
39
26
 
40
27
  .uikit_PlainSelect_uncheckedIcon {
41
- color: #767676;
28
+ color: light-dark(
29
+ var(--mnr-color-gray-4),
30
+ var(--mnr-color-gray-3)
31
+ );
42
32
  }
43
33
 
44
34
  .uikit_PlainSelect_checkedIcon {
45
- color: #337e33;
35
+ color: var(--mnr-color-success);
46
36
  }
47
37
 
48
38
  .uikit_PlainSelect_itemContent {
49
- border-bottom: 1px solid #ddd;
39
+ border-bottom: 1px solid var(--mnr-color-input-border);
50
40
  flex-basis: 100%;
51
41
  flex-shrink: 1;
52
- padding-bottom: 0.25em;
53
- color: #242629;
42
+ padding-bottom: var(--mnr-spacing-xs);
43
+ color: var(--mnr-color-input-text);
54
44
  }
55
45
 
56
46
  .uikit_PlainSelect_description {
57
- margin-top: 0.5em;
58
- color: #737373;
59
- font-size: 87.5%;
47
+ margin-top: var(--mnr-spacing-sm);
48
+ font-size: var(--mnr-font-size-sm);
60
49
  }
@@ -43,7 +43,9 @@ export function Select(props) {
43
43
  onChange: handleChange,
44
44
  onKeyDown: handleKeyDown,
45
45
  onBlur: onBlur || undefined,
46
- children: [selectedItemId == null && _jsx("option", {}), items.map(item => _jsx("option", {
46
+ children: [selectedItemId == null && _jsx("option", {
47
+ children: ''
48
+ }), items.map(item => _jsx("option", {
47
49
  value: item.id,
48
50
  children: item.value
49
51
  }, item.id))]
@@ -54,7 +56,8 @@ export function Select(props) {
54
56
  height: '24',
55
57
  viewBox: '0 0 24 24',
56
58
  children: _jsx("path", {
57
- d: 'M7.406 8.578l4.594 4.594 4.594-4.594 1.406 1.406-6 6-6-6z'
59
+ d: 'M7.406 8.578l4.594 4.594 4.594-4.594 1.406 1.406-6 6-6-6z',
60
+ fill: 'currentColor'
58
61
  })
59
62
  })
60
63
  })]
@@ -1,5 +1,5 @@
1
1
  .uikit_Select_cont:not(:first-child) {
2
- margin-top: 1.5em;
2
+ margin-block-start: var(--mnr-spacing-lg);
3
3
  }
4
4
 
5
5
  .uikit_Select_innerCont {
@@ -10,17 +10,11 @@
10
10
 
11
11
  .uikit_Select_label {
12
12
  display: block;
13
- margin-bottom: 0.25em;
14
- color: #757575;
13
+ margin-block-end: var(--mnr-spacing-xs);
14
+ color: var(--mnr-color-text-on-page-background-secondary);
15
15
  width: 100%;
16
16
  }
17
17
 
18
- @media (prefers-color-scheme: dark) {
19
- .uikit_Select_label {
20
- color: #979797;
21
- }
22
- }
23
-
24
18
  .uikit_Select_selectCont {
25
19
  position: relative;
26
20
  }
@@ -29,40 +23,28 @@
29
23
  display: block;
30
24
  box-sizing: border-box;
31
25
  width: 100%;
32
- border: 1px solid #bcbcbc;
33
- border-radius: 5px;
34
- font-size: 1.2em;
35
- padding: 0.5em 2.5em 0.5em 1em;
36
- color: #242629;
37
- background-color: #fff;
26
+ border: 1px solid var(--mnr-color-input-border);
27
+ border-radius: var(--mnr-radius-md);
28
+ font-size: var(--mnr-font-size-lg);
29
+ padding: var(--mnr-spacing-sm) 2.5rem var(--mnr-spacing-sm) var(--mnr-radius-lg);
30
+ color: var(--mnr-color-input-text);
31
+ background-color: var(--mnr-color-input-background);
38
32
  font-family: inherit;
39
33
  appearance: none;
40
34
  }
41
35
 
42
- @media (prefers-color-scheme: dark) {
43
- .uikit_Select_input {
44
- background-color: #f9f9f9;
45
- border-color: #f9f9f9;
46
- }
47
- }
48
-
49
36
  .uikit_Select_arrowCont {
50
37
  position: absolute;
51
38
  top: 0;
52
- right: 0.5em;
39
+ right: 0.5rem;
53
40
  bottom: 0;
54
41
  display: flex;
55
42
  align-items: center;
43
+ color: currentColor;
56
44
  }
57
45
 
58
46
  .uikit_Select_errorMsg {
59
- color: #a7241b;
60
- font-size: 0.85rem;
61
- margin-top: 0.2em;
62
- }
63
-
64
- @media (prefers-color-scheme: dark) {
65
- .uikit_Select_errorMsg {
66
- color: #fb827a;
67
- }
47
+ color: var(--mnr-color-error);
48
+ font-size: var(--mnr-font-size-sm);
49
+ margin-top: var(--mnr-spacing-xs);
68
50
  }
@@ -0,0 +1,215 @@
1
+ :root,:host {
2
+ color-scheme: light dark;
3
+
4
+ /* Palette colors */
5
+
6
+ --mnr-color-gray-0: #f8f9fa;
7
+ --mnr-color-gray-1: #f1f3f5;
8
+ --mnr-color-gray-2: #e9ecef;
9
+ --mnr-color-gray-3: #dee2e6;
10
+ --mnr-color-gray-4: #ced4da;
11
+ --mnr-color-gray-5: #adb5bd;
12
+ --mnr-color-gray-6: #868e96;
13
+ --mnr-color-gray-7: #495057;
14
+ --mnr-color-gray-8: #343a40;
15
+ --mnr-color-gray-9: #212529;
16
+
17
+ --mnr-color-dark-0: #d8dae0;
18
+ --mnr-color-dark-1: #ccc;
19
+ --mnr-color-dark-2: #767980;
20
+ --mnr-color-dark-3: #6d727d;
21
+ --mnr-color-dark-4: #3b3e43;
22
+ --mnr-color-dark-5: #292c33;
23
+ --mnr-color-dark-6: #22252b;
24
+ --mnr-color-dark-7: #1d2025;
25
+
26
+ --mnr-color-red-0: #fff5f5;
27
+ --mnr-color-red-1: #ffe3e3;
28
+ --mnr-color-red-2: #ffc9c9;
29
+ --mnr-color-red-3: #ffa8a8;
30
+ --mnr-color-red-4: #ff8787;
31
+ --mnr-color-red-5: #ff6b6b;
32
+ --mnr-color-red-6: #fa5252;
33
+ --mnr-color-red-7: #f03e3e;
34
+ --mnr-color-red-8: #e03131;
35
+ --mnr-color-red-9: #c92a2a;
36
+
37
+ --mnr-color-blue-0: #e7f5ff;
38
+ --mnr-color-blue-1: #d0ebff;
39
+ --mnr-color-blue-2: #a5d8ff;
40
+ --mnr-color-blue-3: #74c0fc;
41
+ --mnr-color-blue-4: #4dabf7;
42
+ --mnr-color-blue-5: #339af0;
43
+ --mnr-color-blue-6: #228be6;
44
+ --mnr-color-blue-7: #1c7ed6;
45
+ --mnr-color-blue-8: #1971c2;
46
+ --mnr-color-blue-9: #1864ab;
47
+
48
+ --mnr-color-green-0: #ebfbee;
49
+ --mnr-color-green-1: #d3f9d8;
50
+ --mnr-color-green-2: #b2f2bb;
51
+ --mnr-color-green-3: #8ce99a;
52
+ --mnr-color-green-4: #69db7c;
53
+ --mnr-color-green-5: #51cf66;
54
+ --mnr-color-green-6: #40c057;
55
+ --mnr-color-green-7: #37b24d;
56
+ --mnr-color-green-8: #2f9e44;
57
+ --mnr-color-green-9: #2b8a3e;
58
+
59
+ --mnr-color-orange-0: #fff4e6;
60
+ --mnr-color-orange-1: #ffe8cc;
61
+ --mnr-color-orange-2: #ffd8a8;
62
+ --mnr-color-orange-3: #ffc078;
63
+ --mnr-color-orange-4: #ffa94d;
64
+ --mnr-color-orange-5: #ff922b;
65
+ --mnr-color-orange-6: #fd7e14;
66
+ --mnr-color-orange-7: #f76707;
67
+ --mnr-color-orange-8: #e8590c;
68
+ --mnr-color-orange-9: #d9480f;
69
+
70
+ --mnr-color-yellow-0: #fff9db;
71
+ --mnr-color-yellow-1: #fff3bf;
72
+ --mnr-color-yellow-2: #ffec99;
73
+ --mnr-color-yellow-3: #ffe066;
74
+ --mnr-color-yellow-4: #ffd43b;
75
+ --mnr-color-yellow-5: #fcc419;
76
+ --mnr-color-yellow-6: #fab005;
77
+ --mnr-color-yellow-7: #f59f00;
78
+ --mnr-color-yellow-8: #f08c00;
79
+ --mnr-color-yellow-9: #e67700;
80
+
81
+
82
+ /* Named colors */
83
+
84
+ --mnr-color-white: #fff;
85
+ --mnr-color-accent: #3f72af;
86
+
87
+ --mnr-color-page-background: light-dark(
88
+ var(--mnr-color-gray-0),
89
+ var(--mnr-color-dark-5)
90
+ );
91
+ --mnr-color-text-on-page-background: light-dark(
92
+ var(--mnr-color-gray-9),
93
+ var(--mnr-color-dark-0)
94
+ );
95
+ --mnr-color-text-on-page-background-secondary: light-dark(
96
+ var(--mnr-color-gray-7),
97
+ var(--mnr-color-dark-1)
98
+ );
99
+ --mnr-color-card-background: light-dark(
100
+ var(--mnr-color-white),
101
+ var(--mnr-color-dark-6)
102
+ );
103
+ --mnr-color-card-border: light-dark(
104
+ var(--mnr-color-gray-3),
105
+ var(--mnr-color-dark-2)
106
+ );
107
+ --mnr-color-card-text: light-dark(
108
+ var(--mnr-color-gray-8),
109
+ var(--mnr-color-dark-1)
110
+ );
111
+ --mnr-color-card-text-selection-background: light-dark(
112
+ var(--mnr-color-yellow-2),
113
+ var(--mnr-color-yellow-4)
114
+ );
115
+ --mnr-color-card-text-selection: light-dark(
116
+ var(--mnr-color-gray-8),
117
+ var(--mnr-color-dark-6)
118
+ );
119
+ --mnr-color-card-text-code-background: light-dark(
120
+ var(--mnr-color-gray-2),
121
+ var(--mnr-color-dark-1)
122
+ );
123
+ --mnr-color-card-text-code: light-dark(
124
+ var(--mnr-color-gray-8),
125
+ var(--mnr-color-dark-7)
126
+ );
127
+ --mnr-color-success: light-dark(
128
+ var(--mnr-color-green-9),
129
+ var(--mnr-color-green-7)
130
+ );
131
+ --mnr-color-error: light-dark(
132
+ var(--mnr-color-red-9),
133
+ var(--mnr-color-red-5)
134
+ );
135
+ --mnr-color-input-border: light-dark(
136
+ var(--mnr-color-gray-5),
137
+ var(--mnr-color-dark-2)
138
+ );
139
+ --mnr-color-input-text: light-dark(
140
+ var(--mnr-color-gray-8),
141
+ var(--mnr-color-dark-1)
142
+ );
143
+ --mnr-color-input-background: light-dark(
144
+ var(--mnr-color-white),
145
+ var(--mnr-color-dark-6)
146
+ );
147
+
148
+ /* Fonts */
149
+
150
+ --mnr-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;;
151
+ --mnr-font-family-monospace: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
152
+
153
+ /* Font weights */
154
+ --mnr-font-weight-regular: 400;
155
+ --mnr-font-weight-medium: 600;
156
+ --mnr-font-weight-bold: 700;
157
+
158
+ /* Font sizes */
159
+ --mnr-font-size-xs: .75rem;
160
+ --mnr-font-size-sm: .875rem;
161
+ --mnr-font-size-md: 1rem;
162
+ --mnr-font-size-lg: 1.125rem;
163
+ --mnr-font-size-xl: 1.25rem;
164
+
165
+ /* Line heights */
166
+ --mnr-line-height-xs: 1.4;
167
+ --mnr-line-height-sm: 1.45;
168
+ --mnr-line-height-md: 1.55;
169
+ --mnr-line-height-lg: 1.6;
170
+ --mnr-line-height-xl: 1.65;
171
+
172
+ /* Typography */
173
+
174
+ --mnr-h1-font-size: 2.125rem;
175
+ --mnr-h1-line-height: 1.3;
176
+ --mnr-h1-font-weight: 700;
177
+
178
+ --mnr-h2-font-size: 1.625rem;
179
+ --mnr-h2-line-height: 1.35;
180
+ --mnr-h2-font-weight: 700;
181
+
182
+ --mnr-h3-font-size: 1.375rem;
183
+ --mnr-h3-line-height: 1.4;
184
+ --mnr-h3-font-weight: 700;
185
+
186
+ --mnr-h4-font-size: 1.125rem;
187
+ --mnr-h4-line-height: 1.45;
188
+ --mnr-h4-font-weight: 700;
189
+
190
+ --mnr-h5-font-size: 1rem;
191
+ --mnr-h5-line-height: 1.5;
192
+ --mnr-h5-font-weight: 700;
193
+
194
+ --mnr-h6-font-size: .875rem;
195
+ --mnr-h6-line-height: 1.5;
196
+ --mnr-h6-font-weight: 700;
197
+
198
+ /* Spacing */
199
+
200
+ --mnr-spacing-xs: .25rem;
201
+ --mnr-spacing-sm: .5rem;
202
+ --mnr-spacing-md: 1rem;
203
+ --mnr-spacing-lg: 1.5rem;
204
+ --mnr-spacing-xl: 2rem;
205
+
206
+ /* Radius */
207
+
208
+ --mnr-radius-xs: .125rem;
209
+ --mnr-radius-sm: .25rem;
210
+ --mnr-radius-md: .3125rem;
211
+ --mnr-radius-lg: 1rem;
212
+ --mnr-radius-xl: 2rem;
213
+
214
+
215
+ }
@@ -1,23 +1,11 @@
1
1
  .uikit_Switch_cont {
2
2
  --pin-size: 1.5rem;
3
3
 
4
- --color-label: #757575;
5
- --color-bkg: #d9d9d9;
6
- --color-bkg-on: #3f72af;
7
- --color-border: #aaa;
8
-
9
4
  box-sizing: border-box;
10
5
  display: flex;
11
6
  gap: 1rem;
12
7
  align-items: center;
13
- color: var(--color-label);
14
- }
15
-
16
- @media (prefers-color-scheme: dark) {
17
- .uikit_Switch_cont {
18
- --color-label: #979797;
19
- --color-border: var(--color-bkg);
20
- }
8
+ color: var(--mnr-color-text-on-page-background-secondary);
21
9
  }
22
10
 
23
11
  .uikit_Switch_cont *,
@@ -33,22 +21,25 @@
33
21
  align-items: center;
34
22
  width: calc(var(--pin-size) * 2 + 0.375rem);
35
23
  height: var(--btn-height);
36
- border: 1px solid var(--color-border);
24
+ border: 1px solid var(--mnr-color-input-border);
37
25
  border-radius: calc(var(--btn-height) / 2);
38
26
  padding: 0 0.125rem;
39
- background-color: var(--color-bkg);
27
+ background-color: light-dark(
28
+ var(--mnr-color-gray-2),
29
+ var(--mnr-color-dark-2)
30
+ );
40
31
  outline-offset: 0.25rem;
41
32
  }
42
33
 
43
34
  .uikit_Switch_btn--on {
44
35
  justify-content: flex-end;
45
- background-color: var(--color-bkg-on);
46
- border-color: var(--color-bkg-on);
36
+ background-color: var(--mnr-color-accent);
37
+ border-color: var(--mnr-color-accent);
47
38
  }
48
39
 
49
40
  .uikit_Switch_pin {
50
41
  width: var(--pin-size);
51
42
  height: var(--pin-size);
52
43
  border-radius: 50%;
53
- background-color: #fff;
44
+ background-color: var(--mnr-color-input-background);
54
45
  }
@@ -0,0 +1,22 @@
1
+ import { type JSX } from 'react';
2
+ export type TabPanelProps = {
3
+ /**
4
+ * HTML `id` attribute to be used for linking with the corresponding tab item.
5
+ *
6
+ * Should be generated with the use of React's `useId()` hook and be used both in the tab item
7
+ * and in the corresponding tab panel component.
8
+ */
9
+ htmlId: string;
10
+ /**
11
+ * HTML 'id` of the corresponding tab item.
12
+ */
13
+ htmlTabId: string;
14
+ /** Styles API. */
15
+ classNames?: {
16
+ /** Root element. */
17
+ root?: string;
18
+ };
19
+ /** Contents of the tab. */
20
+ children: React.ReactNode;
21
+ };
22
+ export declare const TabPanel: import("react").MemoExoticComponent<(props: TabPanelProps) => JSX.Element>;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo, useMemo } from 'react';
3
+ export const TabPanel = memo(function TabPanel(props) {
4
+ const {
5
+ htmlId,
6
+ htmlTabId,
7
+ classNames,
8
+ children
9
+ } = props;
10
+ const rootElementCn = useMemo(() => {
11
+ return classNames?.root || undefined;
12
+ }, [classNames]);
13
+ return _jsx("div", {
14
+ id: htmlId,
15
+ role: 'tabpanel',
16
+ tabIndex: 0,
17
+ "aria-labelledby": htmlTabId,
18
+ className: rootElementCn,
19
+ children: children
20
+ });
21
+ });
@@ -0,0 +1,55 @@
1
+ import { type JSX } from 'react';
2
+ import './styles.css';
3
+ export type TabsProps = {
4
+ /** Tab descriptors. */
5
+ tabs: TabItem[];
6
+ /** ID of the currently selected tab. */
7
+ selectedTabId: string;
8
+ /**
9
+ * A11y label for the tabs container.
10
+ * It does not appear in the UI, but required by WCAG.
11
+ */
12
+ tablistLabel: string;
13
+ /**
14
+ * Variant of tabs alignment.
15
+ *
16
+ * - "spread" - tabs are evenly spread over the available width;
17
+ * - "startAlighed" - tabs are aligned to the start of the container.
18
+ *
19
+ * Optional. Defaults to "spread".
20
+ */
21
+ variant?: 'spread' | 'startAlighed';
22
+ /** Styles API. */
23
+ classNames?: {
24
+ /** Root element. */
25
+ root?: string;
26
+ };
27
+ /** Event handler for the event when user activates another tab. */
28
+ onChange: (id: string) => unknown;
29
+ };
30
+ /** Tab item descriptor. */
31
+ export type TabItem = {
32
+ /**
33
+ * Unique key/ID which is used by application logic to decide what tab panel to render.
34
+ *
35
+ * This ID is not used as an HTML `id` attribute (see `htmlId` prop).
36
+ */
37
+ id: string;
38
+ /** Visible label. */
39
+ label: string;
40
+ /**
41
+ * HTML `id` attribute to be used for linking with the corresponding tab panel.
42
+ *
43
+ * Should be generated with the use of React's `useId()` hook and be used both in the tab item
44
+ * and in the corresponding tab panel component.
45
+ */
46
+ htmlId: string;
47
+ /**
48
+ * HTML `id` attribute of the tab panel associated with this tab.
49
+ *
50
+ * This id is used in the `aria-controls` attribute. Should be generated with
51
+ * the use of React's `useId()` hook.
52
+ */
53
+ tabPanelId: string;
54
+ };
55
+ export declare function Tabs(props: TabsProps): JSX.Element;
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo, useCallback, createRef } from 'react';
3
+ import './styles.css';
4
+ export function Tabs(props) {
5
+ const {
6
+ tabs,
7
+ selectedTabId,
8
+ tablistLabel,
9
+ variant = 'spread',
10
+ classNames,
11
+ onChange
12
+ } = props;
13
+ const btnRefs = useMemo(() => tabs.map(() => createRef()), [tabs]);
14
+ const rootContainerClassName = useMemo(() => {
15
+ let cn = 'uikit_Tabs_tabList';
16
+ if (classNames?.root) {
17
+ cn += ` ${classNames.root}`;
18
+ }
19
+ switch (variant) {
20
+ case 'spread':
21
+ break;
22
+ case 'startAlighed':
23
+ cn += ' uikit_Tabs_tabList--startAligned';
24
+ break;
25
+ default:
26
+ {
27
+ const _exhaustiveCheck = variant;
28
+ return _exhaustiveCheck;
29
+ }
30
+ }
31
+ return cn;
32
+ }, [classNames, variant]);
33
+ const handleKeyDownOnTab = useCallback(e => {
34
+ const btn = e.target;
35
+ const id = btn.attributes.getNamedItem('data-id')?.value;
36
+ if (!id) return;
37
+ const currIdx = tabs.findIndex(tab => tab.id === id);
38
+ let nextIdx = null;
39
+ switch (e.key) {
40
+ case 'ArrowRight':
41
+ nextIdx = currIdx + 1;
42
+ if (nextIdx > tabs.length - 1) nextIdx = 0;
43
+ break;
44
+ case 'ArrowLeft':
45
+ nextIdx = currIdx - 1;
46
+ if (nextIdx < 0) nextIdx = tabs.length - 1;
47
+ }
48
+ if (nextIdx == null) return;
49
+ const nextId = tabs[nextIdx]?.id;
50
+ if (!nextId) return;
51
+ btnRefs[nextIdx]?.current?.focus();
52
+ onChange(nextId);
53
+ }, [tabs, btnRefs, onChange]);
54
+ return _jsx("div", {
55
+ className: rootContainerClassName,
56
+ role: 'tablist',
57
+ "aria-label": tablistLabel,
58
+ children: tabs.map((tab, idx) => _jsx("button", {
59
+ ref: btnRefs[idx],
60
+ type: 'button',
61
+ id: tab.htmlId,
62
+ "data-id": tab.id,
63
+ className: 'uikit_Tabs_btn' + (selectedTabId === tab.id ? ' uikit_Tabs_btn--active' : ''),
64
+ role: 'tab',
65
+ "aria-controls": tab.tabPanelId,
66
+ "aria-selected": selectedTabId === tab.id,
67
+ tabIndex: selectedTabId === tab.id ? 0 : -1,
68
+ onClick: () => {
69
+ if (selectedTabId !== tab.id) {
70
+ onChange(tab.id);
71
+ }
72
+ },
73
+ onKeyDown: handleKeyDownOnTab,
74
+ children: _jsx("span", {
75
+ children: tab.label
76
+ })
77
+ }, tab.id))
78
+ });
79
+ }
@@ -0,0 +1,40 @@
1
+ .uikit_Tabs_tabList {
2
+ box-sizing: border-box;
3
+ display: flex;
4
+ justify-content: space-around;
5
+
6
+ & *,
7
+ & :before,
8
+ & :after {
9
+ box-sizing: inherit;
10
+ }
11
+ }
12
+
13
+ .uikit_Tabs_tabList--startAligned {
14
+ justify-content: flex-start;
15
+ gap: var(--mnr-spacing-md);
16
+ }
17
+
18
+ .uikit_Tabs_btn {
19
+ display: block;
20
+ appearance: none;
21
+ color: var(--mnr-color-text-on-page-background-secondary);
22
+ font-family: inherit;
23
+ font-size: var(--mnr-font-size-md);
24
+ font-weight: var(--mnr-font-weight-medium);
25
+ background-color: transparent;
26
+ margin: 0;
27
+ padding: 0;
28
+ padding-block-end: 0.1rem;
29
+ cursor: pointer;
30
+ border: 0;
31
+ border-block-end: 3px solid transparent;
32
+ transition: border-block-end-color 0.4s ease-in-out;
33
+ user-select: none;
34
+ outline-offset: 3px;
35
+ }
36
+
37
+ .uikit_Tabs_btn--active {
38
+ border-block-end-color: var(--mnr-color-accent);
39
+ color: var(--mnr-color-accent);
40
+ }
@@ -2,10 +2,11 @@
2
2
  display: inline;
3
3
  appearance: none;
4
4
  border: none;
5
- color: #3f72af;
5
+ color: var(--mnr-color-accent);
6
6
  background-color: transparent;
7
7
  font-family: inherit;
8
8
  font-size: 1em;
9
+ font-weight: var(--mnr-font-weight-medium);
9
10
  margin: 0;
10
11
  padding: 0;
11
12
  cursor: pointer;
@@ -13,14 +14,8 @@
13
14
  text-align: start;
14
15
  }
15
16
 
16
- @media (prefers-color-scheme: dark) {
17
- .uikit_TextButton_btn {
18
- color: #dbe2ef;
19
- }
20
- }
21
-
22
17
  .uikit_TextButton_innerWrapper {
23
- border-bottom: 1px solid currentColor;
18
+ border-bottom: 2px solid currentColor;
24
19
  border-top: 1px solid transparent;
25
20
  padding-bottom: 0.125em;
26
21
  line-height: 1.4;
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "@peassoft/mnr-web-ui-kit",
3
- "version": "2.4.0",
3
+ "version": "3.0.0",
4
4
  "description": "Peassoft web UI kit for MNR web applications",
5
5
  "type": "module",
6
6
  "exports": {
7
- "./*": "./dist/*"
7
+ "./*": "./dist/*",
8
+ "./styles.css": "./dist/styles.css"
8
9
  },
9
10
  "files": [
10
11
  "dist"
11
12
  ],
12
13
  "scripts": {
13
14
  "dev": "npm run storybook",
14
- "test": "stylelint './src/**/*.css' && eslint ./ && npm run tsc && vitest run",
15
+ "test": "stylelint './**/*.css' && oxlint && npm run tsc && vitest run",
15
16
  "tsc": "tsc --noEmit",
16
17
  "build": "del-cli ./compiled && del-cli ./dist && tsc -p tsconfig-prod.json && babel ./compiled --out-dir ./dist && cpy './src/**/*.css' './dist' && cpy './compiled/**/*.d.ts' './dist'",
17
18
  "storybook": "storybook dev -p 9001 --no-open",
@@ -21,11 +22,11 @@
21
22
  "extends @memnrev/browserslist-config"
22
23
  ],
23
24
  "devDependencies": {
24
- "@babel/cli": "^7.22.15",
25
- "@babel/core": "^7.22.17",
26
- "@babel/preset-env": "^7.22.15",
25
+ "@babel/cli": "^8.0.4",
26
+ "@babel/core": "^8.0.1",
27
+ "@babel/preset-env": "^8.0.2",
27
28
  "@memnrev/browserslist-config": "^0.2.0",
28
- "@memnrev/eslint-v9-config": "^0.4.0",
29
+ "@peassoft/linter": "^2.0.0",
29
30
  "@storybook/addon-a11y": "^10.1.4",
30
31
  "@storybook/addon-docs": "^10.1.4",
31
32
  "@storybook/addon-links": "^10.1.4",
@@ -39,8 +40,6 @@
39
40
  "@vitest/coverage-v8": "^4.0.7",
40
41
  "cpy-cli": "^7.0.0",
41
42
  "del-cli": "^7.0.0",
42
- "eslint": "^9.8.0",
43
- "eslint-plugin-storybook": "^10.1.4",
44
43
  "playwright": "^1.55.1",
45
44
  "react": "^19.0.0",
46
45
  "react-dom": "^19.0.0",
@@ -52,5 +51,9 @@
52
51
  "peerDependencies": {
53
52
  "react": ">= 19 < 20",
54
53
  "react-dom": ">= 19 < 20"
54
+ },
55
+ "allowScripts": {
56
+ "fsevents": true,
57
+ "esbuild": true
55
58
  }
56
59
  }