@peassoft/mnr-web-ui-kit 2.4.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.
@@ -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.4.0",
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": "^7.22.15",
25
- "@babel/core": "^7.22.17",
26
- "@babel/preset-env": "^7.22.15",
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
  }