@monolith-forensics/monolith-ui 1.3.112-dev.1 → 1.3.112-dev.2
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/SegmentedControl/SegmentedControl.d.ts +3 -0
- package/dist/SegmentedControl/SegmentedControl.js +67 -0
- package/dist/SegmentedControl/SegmentedControl.styles.d.ts +18 -0
- package/dist/SegmentedControl/SegmentedControl.styles.js +77 -0
- package/dist/SegmentedControl/SegmentedControl.types.d.ts +25 -0
- package/dist/SegmentedControl/SegmentedControl.types.js +1 -0
- package/dist/SegmentedControl/SegmentedControl.utils.d.ts +11 -0
- package/dist/SegmentedControl/SegmentedControl.utils.js +84 -0
- package/dist/SegmentedControl/index.d.ts +1 -0
- package/dist/SegmentedControl/index.js +1 -0
- package/dist/SegmentedControl/useSegmentedKeyboardNav.d.ts +12 -0
- package/dist/SegmentedControl/useSegmentedKeyboardNav.js +53 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { useUncontrolled } from "@mantine/hooks";
|
|
14
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
15
|
+
import { getFirstEnabledIndex, normalizeSegmentedData } from "./SegmentedControl.utils";
|
|
16
|
+
import { StyledIndicator, StyledRoot, StyledSegmentButton, } from "./SegmentedControl.styles";
|
|
17
|
+
import { useSegmentedKeyboardNav } from "./useSegmentedKeyboardNav";
|
|
18
|
+
export const SegmentedControl = (_a) => {
|
|
19
|
+
var { data, value, defaultValue, onChange, size = "sm", fullWidth = false, disabled = false, variant = "outlined", activeColor, name, className, style, onKeyDown } = _a, other = __rest(_a, ["data", "value", "defaultValue", "onChange", "size", "fullWidth", "disabled", "variant", "activeColor", "name", "className", "style", "onKeyDown"]);
|
|
20
|
+
const normalizedData = useMemo(() => normalizeSegmentedData(data), [data]);
|
|
21
|
+
const firstEnabledValue = useMemo(() => { var _a, _b; return (_b = (_a = normalizedData.find((item) => !item.disabled)) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ""; }, [normalizedData]);
|
|
22
|
+
const [_value, handleChange] = useUncontrolled({
|
|
23
|
+
value,
|
|
24
|
+
defaultValue,
|
|
25
|
+
finalValue: firstEnabledValue,
|
|
26
|
+
onChange,
|
|
27
|
+
});
|
|
28
|
+
const activeIndex = normalizedData.findIndex((item) => item.value === _value);
|
|
29
|
+
const selectedItem = normalizedData[activeIndex];
|
|
30
|
+
const buttonRefs = useRef([]);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (normalizedData.length === 0)
|
|
33
|
+
return;
|
|
34
|
+
if (selectedItem && !selectedItem.disabled)
|
|
35
|
+
return;
|
|
36
|
+
const firstEnabled = normalizedData.find((item) => !item.disabled);
|
|
37
|
+
if (!firstEnabled)
|
|
38
|
+
return;
|
|
39
|
+
if (_value !== firstEnabled.value) {
|
|
40
|
+
handleChange(firstEnabled.value);
|
|
41
|
+
}
|
|
42
|
+
}, [normalizedData, selectedItem, _value, handleChange]);
|
|
43
|
+
const handleSelect = (item) => {
|
|
44
|
+
if (disabled || item.disabled)
|
|
45
|
+
return;
|
|
46
|
+
handleChange(item.value);
|
|
47
|
+
};
|
|
48
|
+
const handleKeyDown = useSegmentedKeyboardNav({
|
|
49
|
+
disabled,
|
|
50
|
+
data: normalizedData,
|
|
51
|
+
activeIndex,
|
|
52
|
+
onChange: handleChange,
|
|
53
|
+
buttonRefs,
|
|
54
|
+
onKeyDown,
|
|
55
|
+
});
|
|
56
|
+
const columnCount = Math.max(normalizedData.length, 1);
|
|
57
|
+
const firstEnabledIndex = getFirstEnabledIndex(normalizedData);
|
|
58
|
+
return (_jsxs(StyledRoot, Object.assign({ role: "radiogroup", "aria-disabled": disabled, className: className, style: Object.assign({ gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))` }, style), "$fullWidth": fullWidth, "$disabled": disabled, onKeyDown: handleKeyDown }, other, { children: [name && _jsx("input", { type: "hidden", name: name, value: _value || "" }), activeIndex >= 0 && !(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.disabled) && (_jsx(StyledIndicator, { "$index": activeIndex, "$count": columnCount, "$variant": variant, "$activeColor": activeColor })), normalizedData.map((item, index) => {
|
|
59
|
+
const isActive = item.value === _value && !item.disabled;
|
|
60
|
+
const isSegmentDisabled = disabled || item.disabled;
|
|
61
|
+
return (_jsx(StyledSegmentButton, { ref: (node) => {
|
|
62
|
+
buttonRefs.current[index] = node;
|
|
63
|
+
}, type: "button", role: "radio", "aria-checked": isActive, disabled: isSegmentDisabled, tabIndex: isActive || (activeIndex < 0 && index === firstEnabledIndex)
|
|
64
|
+
? 0
|
|
65
|
+
: -1, "$size": size, "$active": isActive, "$variant": variant, "$activeColor": activeColor, onClick: () => handleSelect(item), children: item.label }, item.value));
|
|
66
|
+
})] })));
|
|
67
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Size } from "../core";
|
|
2
|
+
import { SegmentedControlVariant } from "./SegmentedControl.types";
|
|
3
|
+
export declare const StyledRoot: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
|
|
4
|
+
$fullWidth: boolean;
|
|
5
|
+
$disabled: boolean;
|
|
6
|
+
}>> & string;
|
|
7
|
+
export declare const StyledIndicator: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
|
|
8
|
+
$index: number;
|
|
9
|
+
$count: number;
|
|
10
|
+
$variant: SegmentedControlVariant;
|
|
11
|
+
$activeColor?: string;
|
|
12
|
+
}>> & string;
|
|
13
|
+
export declare const StyledSegmentButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, {
|
|
14
|
+
$size?: Size;
|
|
15
|
+
$active: boolean;
|
|
16
|
+
$variant: SegmentedControlVariant;
|
|
17
|
+
$activeColor?: string;
|
|
18
|
+
}>> & string;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import styled from "styled-components";
|
|
2
|
+
import { getSegmentFontSize, getSegmentHeight, resolveActiveColor, } from "./SegmentedControl.utils";
|
|
3
|
+
export const StyledRoot = styled.div `
|
|
4
|
+
user-select: none;
|
|
5
|
+
position: relative;
|
|
6
|
+
display: grid;
|
|
7
|
+
align-items: center;
|
|
8
|
+
width: ${({ $fullWidth }) => ($fullWidth ? "100%" : "fit-content")};
|
|
9
|
+
min-width: ${({ $fullWidth }) => ($fullWidth ? "0" : "200px")};
|
|
10
|
+
padding: 3px;
|
|
11
|
+
border-radius: 8px;
|
|
12
|
+
border: 1px solid ${({ theme }) => theme.palette.action.hover};
|
|
13
|
+
background: ${({ theme }) => theme.palette.background.secondary};
|
|
14
|
+
opacity: ${({ $disabled }) => ($disabled ? 0.6 : 1)};
|
|
15
|
+
pointer-events: ${({ $disabled }) => ($disabled ? "none" : "auto")};
|
|
16
|
+
`;
|
|
17
|
+
export const StyledIndicator = styled.div `
|
|
18
|
+
position: absolute;
|
|
19
|
+
top: 3px;
|
|
20
|
+
left: 3px;
|
|
21
|
+
z-index: 1;
|
|
22
|
+
height: calc(100% - 6px);
|
|
23
|
+
width: ${({ $count }) => `calc((100% - 6px) / ${$count})`};
|
|
24
|
+
border-radius: 6px;
|
|
25
|
+
border: 1px solid
|
|
26
|
+
${({ theme, $variant, $activeColor }) => {
|
|
27
|
+
const resolved = resolveActiveColor(theme, $activeColor);
|
|
28
|
+
return $variant === "contained" ? resolved.main : resolved.main;
|
|
29
|
+
}};
|
|
30
|
+
background: ${({ theme, $variant, $activeColor }) => {
|
|
31
|
+
const resolved = resolveActiveColor(theme, $activeColor);
|
|
32
|
+
return $variant === "contained"
|
|
33
|
+
? resolved.main
|
|
34
|
+
: theme.palette.background.paper;
|
|
35
|
+
}};
|
|
36
|
+
transform: translateX(${({ $index }) => `${$index * 100}%`});
|
|
37
|
+
transition: transform 160ms ease;
|
|
38
|
+
`;
|
|
39
|
+
export const StyledSegmentButton = styled.button `
|
|
40
|
+
position: relative;
|
|
41
|
+
z-index: 2;
|
|
42
|
+
border: none;
|
|
43
|
+
outline: none;
|
|
44
|
+
padding: 0 12px;
|
|
45
|
+
margin: 0;
|
|
46
|
+
border-radius: 6px;
|
|
47
|
+
background: transparent;
|
|
48
|
+
color: ${({ theme, $active, $variant, $activeColor }) => {
|
|
49
|
+
const resolved = resolveActiveColor(theme, $activeColor);
|
|
50
|
+
if (!$active)
|
|
51
|
+
return theme.palette.text.secondary;
|
|
52
|
+
return $variant === "contained" ? resolved.contrastText : resolved.main;
|
|
53
|
+
}};
|
|
54
|
+
font-size: ${({ $size }) => getSegmentFontSize($size)};
|
|
55
|
+
font-weight: 500;
|
|
56
|
+
line-height: 1;
|
|
57
|
+
height: ${({ $size }) => `${getSegmentHeight($size)}px`};
|
|
58
|
+
white-space: nowrap;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
transition:
|
|
61
|
+
color 160ms ease,
|
|
62
|
+
background-color 160ms ease;
|
|
63
|
+
|
|
64
|
+
&:hover {
|
|
65
|
+
background: ${({ theme, $active }) => $active ? "transparent" : theme.palette.action.hover};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
&:focus-visible {
|
|
69
|
+
box-shadow: inset 0 0 0 1px ${({ theme }) => theme.palette.primary.main};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
&:disabled {
|
|
73
|
+
cursor: not-allowed;
|
|
74
|
+
color: ${({ theme }) => theme.palette.text.disabled};
|
|
75
|
+
background: transparent;
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
import { Size } from "../core";
|
|
3
|
+
export type NormalizedSegmentedItem = {
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
value: string;
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type SegmentedControlDataItem = string | {
|
|
9
|
+
label: ReactNode;
|
|
10
|
+
value: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type SegmentedControlVariant = "outlined" | "contained";
|
|
14
|
+
export interface SegmentedControlProps extends Omit<HTMLAttributes<HTMLDivElement>, "defaultValue" | "onChange"> {
|
|
15
|
+
data: SegmentedControlDataItem[];
|
|
16
|
+
value?: string;
|
|
17
|
+
defaultValue?: string;
|
|
18
|
+
onChange?: (value: string) => void;
|
|
19
|
+
size?: Size;
|
|
20
|
+
fullWidth?: boolean;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
name?: string;
|
|
23
|
+
variant?: SegmentedControlVariant;
|
|
24
|
+
activeColor?: string;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Size } from "../core";
|
|
2
|
+
import { NormalizedSegmentedItem, SegmentedControlDataItem } from "./SegmentedControl.types";
|
|
3
|
+
export declare const resolveActiveColor: (theme: any, activeColor?: string) => {
|
|
4
|
+
main: any;
|
|
5
|
+
contrastText: any;
|
|
6
|
+
};
|
|
7
|
+
export declare const getSegmentHeight: (size?: Size) => 32 | 38 | 22 | 26 | 46 | 56;
|
|
8
|
+
export declare const getSegmentFontSize: (size?: Size) => "11px" | "12px" | "14px" | "16px" | "18px" | "20px";
|
|
9
|
+
export declare const normalizeSegmentedData: (data: SegmentedControlDataItem[]) => NormalizedSegmentedItem[];
|
|
10
|
+
export declare const getFirstEnabledIndex: (data: NormalizedSegmentedItem[]) => number;
|
|
11
|
+
export declare const getNextEnabledIndex: (data: NormalizedSegmentedItem[], startIndex: number, direction: 1 | -1) => number;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export const resolveActiveColor = (theme, activeColor) => {
|
|
2
|
+
var _a;
|
|
3
|
+
const fallback = {
|
|
4
|
+
main: theme.palette.primary.main,
|
|
5
|
+
contrastText: theme.palette.primary.contrastText,
|
|
6
|
+
};
|
|
7
|
+
if (!activeColor)
|
|
8
|
+
return fallback;
|
|
9
|
+
const paletteCandidate = (_a = theme.palette) === null || _a === void 0 ? void 0 : _a[activeColor];
|
|
10
|
+
if (paletteCandidate &&
|
|
11
|
+
typeof paletteCandidate === "object" &&
|
|
12
|
+
"main" in paletteCandidate) {
|
|
13
|
+
return {
|
|
14
|
+
main: paletteCandidate.main,
|
|
15
|
+
contrastText: paletteCandidate.contrastText || fallback.contrastText,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
main: activeColor,
|
|
20
|
+
contrastText: fallback.contrastText,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export const getSegmentHeight = (size) => {
|
|
24
|
+
switch (size) {
|
|
25
|
+
case "xxs":
|
|
26
|
+
return 22;
|
|
27
|
+
case "xs":
|
|
28
|
+
return 26;
|
|
29
|
+
case "md":
|
|
30
|
+
return 38;
|
|
31
|
+
case "lg":
|
|
32
|
+
return 46;
|
|
33
|
+
case "xl":
|
|
34
|
+
return 56;
|
|
35
|
+
case "sm":
|
|
36
|
+
default:
|
|
37
|
+
return 32;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
export const getSegmentFontSize = (size) => {
|
|
41
|
+
switch (size) {
|
|
42
|
+
case "xxs":
|
|
43
|
+
return "11px";
|
|
44
|
+
case "xs":
|
|
45
|
+
return "12px";
|
|
46
|
+
case "md":
|
|
47
|
+
return "16px";
|
|
48
|
+
case "lg":
|
|
49
|
+
return "18px";
|
|
50
|
+
case "xl":
|
|
51
|
+
return "20px";
|
|
52
|
+
case "sm":
|
|
53
|
+
default:
|
|
54
|
+
return "14px";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
export const normalizeSegmentedData = (data) => data.map((item) => {
|
|
58
|
+
var _a;
|
|
59
|
+
return typeof item === "string"
|
|
60
|
+
? {
|
|
61
|
+
label: item,
|
|
62
|
+
value: item,
|
|
63
|
+
disabled: false,
|
|
64
|
+
}
|
|
65
|
+
: {
|
|
66
|
+
label: item.label,
|
|
67
|
+
value: item.value,
|
|
68
|
+
disabled: (_a = item.disabled) !== null && _a !== void 0 ? _a : false,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
export const getFirstEnabledIndex = (data) => data.findIndex((item) => item.disabled === false);
|
|
72
|
+
export const getNextEnabledIndex = (data, startIndex, direction) => {
|
|
73
|
+
var _a;
|
|
74
|
+
if (data.length === 0)
|
|
75
|
+
return -1;
|
|
76
|
+
let index = startIndex;
|
|
77
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
78
|
+
index = (index + direction + data.length) % data.length;
|
|
79
|
+
if (!((_a = data[index]) === null || _a === void 0 ? void 0 : _a.disabled)) {
|
|
80
|
+
return index;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return -1;
|
|
84
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./SegmentedControl";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./SegmentedControl";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { KeyboardEvent, KeyboardEventHandler, MutableRefObject } from "react";
|
|
2
|
+
import { NormalizedSegmentedItem } from "./SegmentedControl.types";
|
|
3
|
+
type UseSegmentedKeyboardNavProps = {
|
|
4
|
+
disabled: boolean;
|
|
5
|
+
data: NormalizedSegmentedItem[];
|
|
6
|
+
activeIndex: number;
|
|
7
|
+
onChange: (value: string) => void;
|
|
8
|
+
buttonRefs: MutableRefObject<Array<HTMLButtonElement | null>>;
|
|
9
|
+
onKeyDown?: KeyboardEventHandler<HTMLDivElement>;
|
|
10
|
+
};
|
|
11
|
+
export declare const useSegmentedKeyboardNav: ({ disabled, data, activeIndex, onChange, buttonRefs, onKeyDown, }: UseSegmentedKeyboardNavProps) => (event: KeyboardEvent<HTMLDivElement>) => void;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useCallback, } from "react";
|
|
2
|
+
import { getFirstEnabledIndex, getNextEnabledIndex } from "./SegmentedControl.utils";
|
|
3
|
+
const getLastEnabledIndex = (data) => {
|
|
4
|
+
const reverseIndex = [...data].reverse().findIndex((item) => !item.disabled);
|
|
5
|
+
return reverseIndex < 0 ? -1 : data.length - 1 - reverseIndex;
|
|
6
|
+
};
|
|
7
|
+
export const useSegmentedKeyboardNav = ({ disabled, data, activeIndex, onChange, buttonRefs, onKeyDown, }) => {
|
|
8
|
+
return useCallback((event) => {
|
|
9
|
+
var _a, _b, _c;
|
|
10
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
|
|
11
|
+
if (event.defaultPrevented || disabled || data.length === 0)
|
|
12
|
+
return;
|
|
13
|
+
const firstEnabledIndex = getFirstEnabledIndex(data);
|
|
14
|
+
if (firstEnabledIndex < 0)
|
|
15
|
+
return;
|
|
16
|
+
if (event.key === "Home") {
|
|
17
|
+
event.preventDefault();
|
|
18
|
+
const target = data[firstEnabledIndex];
|
|
19
|
+
if (target) {
|
|
20
|
+
onChange(target.value);
|
|
21
|
+
(_a = buttonRefs.current[firstEnabledIndex]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
22
|
+
}
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (event.key === "End") {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
const lastEnabledIndex = getLastEnabledIndex(data);
|
|
28
|
+
const target = data[lastEnabledIndex];
|
|
29
|
+
if (target) {
|
|
30
|
+
onChange(target.value);
|
|
31
|
+
(_b = buttonRefs.current[lastEnabledIndex]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const directionKeys = {
|
|
36
|
+
ArrowRight: 1,
|
|
37
|
+
ArrowDown: 1,
|
|
38
|
+
ArrowLeft: -1,
|
|
39
|
+
ArrowUp: -1,
|
|
40
|
+
};
|
|
41
|
+
const direction = directionKeys[event.key];
|
|
42
|
+
if (!direction)
|
|
43
|
+
return;
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
const startIndex = activeIndex >= 0 ? activeIndex : firstEnabledIndex;
|
|
46
|
+
const nextIndex = getNextEnabledIndex(data, startIndex, direction);
|
|
47
|
+
const target = data[nextIndex];
|
|
48
|
+
if (target) {
|
|
49
|
+
onChange(target.value);
|
|
50
|
+
(_c = buttonRefs.current[nextIndex]) === null || _c === void 0 ? void 0 : _c.focus();
|
|
51
|
+
}
|
|
52
|
+
}, [activeIndex, buttonRefs, data, disabled, onChange, onKeyDown]);
|
|
53
|
+
};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED