@peassoft/mnr-web-ui-kit 2.3.0 → 2.5.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/dist/plain-select/icons.d.ts +3 -0
- package/dist/plain-select/icons.js +39 -0
- package/dist/plain-select/index.js +2 -4
- package/dist/plain-select/styles.css +9 -5
- package/dist/tab-panel/index.d.ts +22 -0
- package/dist/tab-panel/index.js +21 -0
- package/dist/tabs/index.d.ts +55 -0
- package/dist/tabs/index.js +79 -0
- package/dist/tabs/styles.css +48 -0
- package/package.json +8 -4
- package/dist/plain-select/icon.d.ts +0 -6
- package/dist/plain-select/icon.js +0 -17
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export function UncheckedIcon() {
|
|
3
|
+
return _jsx("svg", {
|
|
4
|
+
width: '24',
|
|
5
|
+
height: '24',
|
|
6
|
+
viewBox: '0 0 24 24',
|
|
7
|
+
fill: 'none',
|
|
8
|
+
stroke: 'currentColor',
|
|
9
|
+
strokeWidth: '1',
|
|
10
|
+
strokeLinecap: 'round',
|
|
11
|
+
strokeLinejoin: 'round',
|
|
12
|
+
className: 'uikit_PlainSelect_icon uikit_PlainSelect_uncheckedIcon',
|
|
13
|
+
children: _jsx("rect", {
|
|
14
|
+
width: '18',
|
|
15
|
+
height: '18',
|
|
16
|
+
x: '3',
|
|
17
|
+
y: '3',
|
|
18
|
+
rx: '2'
|
|
19
|
+
})
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export function CheckedIcon() {
|
|
23
|
+
return _jsxs("svg", {
|
|
24
|
+
width: '24',
|
|
25
|
+
height: '24',
|
|
26
|
+
viewBox: '0 0 24 24',
|
|
27
|
+
fill: 'none',
|
|
28
|
+
stroke: 'currentColor',
|
|
29
|
+
strokeWidth: '2',
|
|
30
|
+
strokeLinecap: 'round',
|
|
31
|
+
strokeLinejoin: 'round',
|
|
32
|
+
className: 'uikit_PlainSelect_icon uikit_PlainSelect_checkedIcon',
|
|
33
|
+
children: [_jsx("path", {
|
|
34
|
+
d: 'M21 10.656V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h12.344'
|
|
35
|
+
}), _jsx("path", {
|
|
36
|
+
d: 'm9 11 3 3L22 4'
|
|
37
|
+
})]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useId, useCallback, useState, useMemo, useLayoutEffect, createRef } from 'react';
|
|
3
3
|
import './styles.css';
|
|
4
|
-
import
|
|
4
|
+
import { UncheckedIcon, CheckedIcon } from './icons.js';
|
|
5
5
|
/**
|
|
6
6
|
* Select element implementing Listbox pattern. Presents a list of options and allows
|
|
7
7
|
* a user to select one or more of them.
|
|
@@ -177,9 +177,7 @@ export function PlainSelect(props) {
|
|
|
177
177
|
tabIndex: multiple && currFocusedIdx === idx ? 0 : -1,
|
|
178
178
|
onClick: () => handleItemClick(item.id),
|
|
179
179
|
onKeyDown: multiple ? handleMultiSelectKeyDown : undefined,
|
|
180
|
-
children: [_jsx(
|
|
181
|
-
selected: isSelected
|
|
182
|
-
}), _jsxs("div", {
|
|
180
|
+
children: [isSelected ? _jsx(CheckedIcon, {}) : _jsx(UncheckedIcon, {}), _jsxs("div", {
|
|
183
181
|
className: 'uikit_PlainSelect_itemContent',
|
|
184
182
|
children: [_jsx("div", {
|
|
185
183
|
children: item.title
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
.uikit_PlainSelect_label {
|
|
2
2
|
margin: 0;
|
|
3
|
-
color: #
|
|
3
|
+
color: #737373;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
@media (prefers-color-scheme: dark) {
|
|
7
7
|
.uikit_PlainSelect_label {
|
|
8
|
-
color: #
|
|
8
|
+
color: #acacac;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -37,8 +37,12 @@
|
|
|
37
37
|
flex-shrink: 0;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
.
|
|
41
|
-
|
|
40
|
+
.uikit_PlainSelect_uncheckedIcon {
|
|
41
|
+
color: #767676;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.uikit_PlainSelect_checkedIcon {
|
|
45
|
+
color: #337e33;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
.uikit_PlainSelect_itemContent {
|
|
@@ -51,6 +55,6 @@
|
|
|
51
55
|
|
|
52
56
|
.uikit_PlainSelect_description {
|
|
53
57
|
margin-top: 0.5em;
|
|
54
|
-
color: #
|
|
58
|
+
color: #737373;
|
|
55
59
|
font-size: 87.5%;
|
|
56
60
|
}
|
|
@@ -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,48 @@
|
|
|
1
|
+
.uikit_Tabs_tabList {
|
|
2
|
+
--color-label: #242629;
|
|
3
|
+
--color-active-bar: #3f72af;
|
|
4
|
+
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
display: flex;
|
|
7
|
+
justify-content: space-around;
|
|
8
|
+
|
|
9
|
+
& *,
|
|
10
|
+
& :before,
|
|
11
|
+
& :after {
|
|
12
|
+
box-sizing: inherit;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@media (prefers-color-scheme: dark) {
|
|
17
|
+
.uikit_Tabs_tabList {
|
|
18
|
+
--color-label: #f9f9f9;
|
|
19
|
+
--color-active-bar: #dbe2ef;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.uikit_Tabs_tabList--startAligned {
|
|
24
|
+
justify-content: flex-start;
|
|
25
|
+
gap: 1em;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.uikit_Tabs_btn {
|
|
29
|
+
display: block;
|
|
30
|
+
appearance: none;
|
|
31
|
+
color: var(--color-label);
|
|
32
|
+
font-family: inherit;
|
|
33
|
+
font-size: 1em;
|
|
34
|
+
background-color: transparent;
|
|
35
|
+
margin: 0;
|
|
36
|
+
padding: 0;
|
|
37
|
+
padding-block-end: 0.1em;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
border: 0;
|
|
40
|
+
border-block-end: 3px solid transparent;
|
|
41
|
+
transition: border-block-end-color 0.4s ease-in-out;
|
|
42
|
+
user-select: none;
|
|
43
|
+
outline-offset: 3px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.uikit_Tabs_btn--active {
|
|
47
|
+
border-block-end-color: var(--color-active-bar);
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peassoft/mnr-web-ui-kit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Peassoft web UI kit for MNR web applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"extends @memnrev/browserslist-config"
|
|
22
22
|
],
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@babel/cli": "^
|
|
25
|
-
"@babel/core": "^
|
|
26
|
-
"@babel/preset-env": "^
|
|
24
|
+
"@babel/cli": "^8.0.4",
|
|
25
|
+
"@babel/core": "^8.0.1",
|
|
26
|
+
"@babel/preset-env": "^8.0.2",
|
|
27
27
|
"@memnrev/browserslist-config": "^0.2.0",
|
|
28
28
|
"@memnrev/eslint-v9-config": "^0.4.0",
|
|
29
29
|
"@storybook/addon-a11y": "^10.1.4",
|
|
@@ -52,5 +52,9 @@
|
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"react": ">= 19 < 20",
|
|
54
54
|
"react-dom": ">= 19 < 20"
|
|
55
|
+
},
|
|
56
|
+
"allowScripts": {
|
|
57
|
+
"fsevents": true,
|
|
58
|
+
"esbuild": true
|
|
55
59
|
}
|
|
56
60
|
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
export default function Icon(props) {
|
|
3
|
-
const {
|
|
4
|
-
selected
|
|
5
|
-
} = props;
|
|
6
|
-
const className = 'uikit_PlainSelect_icon' + (selected ? '' : ' uikit_PlainSelect_icon--hidden');
|
|
7
|
-
return _jsx("svg", {
|
|
8
|
-
className: className,
|
|
9
|
-
width: '24',
|
|
10
|
-
height: '24',
|
|
11
|
-
viewBox: '0 0 24 24',
|
|
12
|
-
children: _jsx("path", {
|
|
13
|
-
d: 'M9 16.172l10.594-10.594 1.406 1.406-12 12-5.578-5.578 1.406-1.406z',
|
|
14
|
-
fill: '#24b23e'
|
|
15
|
-
})
|
|
16
|
-
});
|
|
17
|
-
}
|