@monolith-forensics/monolith-ui 1.3.112-dev.0 → 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.
@@ -1,2 +1,2 @@
1
1
  import { KanbanGroupKey, KanbanProps } from "./types";
2
- export declare const Kanban: <TCard, TGroup extends KanbanGroupKey>({ cards, getCardId, getCardGroup, getCardOrder, renderCard, onCardsReorder, groupOrder, groupLabels, renderColumnHeader, columnWidth, columnHeight, estimatedCardHeight, overscanCount, orderStep, minOrderGap, emptyColumnPlaceholder, className, }: KanbanProps<TCard, TGroup>) => import("react/jsx-runtime").JSX.Element;
2
+ export declare const Kanban: <TCard, TGroup extends KanbanGroupKey>({ cards, getCardId, getCardGroup, getCardOrder, renderCard, onCardsReorder, groupOrder, groupLabels, renderColumnHeader, columnWidth, columnGap, cardGap, columnHeight, estimatedCardHeight, overscanCount, orderStep, minOrderGap, emptyColumnPlaceholder, className, }: KanbanProps<TCard, TGroup>) => import("react/jsx-runtime").JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { DndContext, DragOverlay, KeyboardSensor, MeasuringStrategy, PointerSensor, closestCenter, getFirstCollision, pointerWithin, rectIntersection, useSensor, useSensors, } from "@dnd-kit/core";
3
3
  import { sortableKeyboardCoordinates } from "@dnd-kit/sortable";
4
- import { useCallback, useEffect, useRef, useState, } from "react";
4
+ import { useCallback, useEffect, useRef, useState } from "react";
5
5
  import { KanbanColumn } from "./KanbanColumn";
6
6
  import { findContainer, fromCardToken, isCardToken, isGroupToken, moveCard, moveCardToInsertIndex, } from "./board";
7
7
  import { dropAnimation } from "./constants";
@@ -9,7 +9,7 @@ import { CardContainer, Board } from "./styles";
9
9
  import { useKanbanBoardData } from "./useKanbanBoardData";
10
10
  import { useKanbanRowHeights } from "./useKanbanRowHeights";
11
11
  import { DEFAULT_MIN_ORDER_GAP, DEFAULT_ORDER_STEP, calculateOrderValue, } from "./utils";
12
- export const Kanban = ({ cards, getCardId, getCardGroup, getCardOrder, renderCard, onCardsReorder, groupOrder, groupLabels, renderColumnHeader, columnWidth = 320, columnHeight, estimatedCardHeight = 100, overscanCount = 4, orderStep = DEFAULT_ORDER_STEP, minOrderGap = DEFAULT_MIN_ORDER_GAP, emptyColumnPlaceholder, className, }) => {
12
+ export const Kanban = ({ cards, getCardId, getCardGroup, getCardOrder, renderCard, onCardsReorder, groupOrder, groupLabels, renderColumnHeader, columnWidth = 320, columnGap = 12, cardGap = 8, columnHeight, estimatedCardHeight = 100, overscanCount = 4, orderStep = DEFAULT_ORDER_STEP, minOrderGap = DEFAULT_MIN_ORDER_GAP, emptyColumnPlaceholder, className, }) => {
13
13
  var _a, _b;
14
14
  const [activeCardToken, setActiveCardToken] = useState(null);
15
15
  const [activeOverlayWidth, setActiveOverlayWidth] = useState(null);
@@ -214,14 +214,14 @@ export const Kanban = ({ cards, getCardId, getCardGroup, getCardOrder, renderCar
214
214
  droppable: {
215
215
  strategy: MeasuringStrategy.Always,
216
216
  },
217
- }, onDragStart: handleDragStart, onDragOver: handleDragOver, onDragEnd: handleDragEnd, onDragCancel: handleDragCancel, children: [_jsx(Board, { className: className, children: Object.keys(board).map((columnToken) => {
217
+ }, onDragStart: handleDragStart, onDragOver: handleDragOver, onDragEnd: handleDragEnd, onDragCancel: handleDragCancel, children: [_jsx(Board, { className: className, "$columnGap": columnGap, children: Object.keys(board).map((columnToken) => {
218
218
  var _a, _b;
219
219
  const columnItems = (_a = board[columnToken]) !== null && _a !== void 0 ? _a : [];
220
220
  const groupValue = groupValueByToken.get(columnToken);
221
221
  if (groupValue == null) {
222
222
  return null;
223
223
  }
224
- return (_jsx(KanbanColumn, { columnToken: columnToken, groupValue: groupValue, groupLabel: groupLabels === null || groupLabels === void 0 ? void 0 : groupLabels[String(groupValue)], renderColumnHeader: renderColumnHeader, itemTokens: columnItems, cardByToken: cardByToken, cardIdByToken: cardIdByToken, groupByToken: groupValueByToken, renderCard: renderCard, onMeasure: onRowMeasured, rowHeights: (_b = columnHeights[columnToken]) !== null && _b !== void 0 ? _b : {}, listRefMap: listRefMap, columnWidth: columnWidth, columnHeight: columnHeight, estimatedCardHeight: estimatedCardHeight, overscanCount: overscanCount, emptyColumnPlaceholder: emptyColumnPlaceholder }, columnToken));
224
+ return (_jsx(KanbanColumn, { columnToken: columnToken, groupValue: groupValue, groupLabel: groupLabels === null || groupLabels === void 0 ? void 0 : groupLabels[String(groupValue)], renderColumnHeader: renderColumnHeader, itemTokens: columnItems, cardByToken: cardByToken, cardIdByToken: cardIdByToken, groupByToken: groupValueByToken, renderCard: renderCard, onMeasure: onRowMeasured, rowHeights: (_b = columnHeights[columnToken]) !== null && _b !== void 0 ? _b : {}, listRefMap: listRefMap, columnWidth: columnWidth, columnHeight: columnHeight, cardGap: cardGap, estimatedCardHeight: estimatedCardHeight, overscanCount: overscanCount, emptyColumnPlaceholder: emptyColumnPlaceholder }, columnToken));
225
225
  }) }), _jsx(DragOverlay, { adjustScale: false, dropAnimation: dropAnimation, children: activeCard && activeGroup != null ? (_jsx("div", { style: { width: activeOverlayWidth !== null && activeOverlayWidth !== void 0 ? activeOverlayWidth : undefined }, children: _jsx(CardContainer, { "$isDragging": true, "$isDragOverlay": true, children: renderCard({
226
226
  card: activeCard,
227
227
  cardId: getCardId(activeCard),
@@ -16,8 +16,9 @@ export interface KanbanColumnProps<TCard, TGroup extends KanbanGroupKey> {
16
16
  listRefMap: MutableRefObject<Record<string, VariableSizeList | null>>;
17
17
  columnWidth: number;
18
18
  columnHeight?: number;
19
+ cardGap: number;
19
20
  estimatedCardHeight: number;
20
21
  overscanCount: number;
21
22
  emptyColumnPlaceholder?: ReactNode;
22
23
  }
23
- export declare const KanbanColumn: <TCard, TGroup extends KanbanGroupKey>({ columnToken, groupValue, groupLabel, renderColumnHeader, itemTokens, cardByToken, cardIdByToken, groupByToken, renderCard, onMeasure, rowHeights, listRefMap, columnWidth, columnHeight, estimatedCardHeight, overscanCount, emptyColumnPlaceholder, }: KanbanColumnProps<TCard, TGroup>) => import("react/jsx-runtime").JSX.Element;
24
+ export declare const KanbanColumn: <TCard, TGroup extends KanbanGroupKey>({ columnToken, groupValue, groupLabel, renderColumnHeader, itemTokens, cardByToken, cardIdByToken, groupByToken, renderCard, onMeasure, rowHeights, listRefMap, columnWidth, columnHeight, cardGap, estimatedCardHeight, overscanCount, emptyColumnPlaceholder, }: KanbanColumnProps<TCard, TGroup>) => import("react/jsx-runtime").JSX.Element;
@@ -6,9 +6,8 @@ import { useOverlayScrollbars } from "overlayscrollbars-react";
6
6
  import AutoSizer from "react-virtualized-auto-sizer";
7
7
  import { VariableSizeList } from "react-window";
8
8
  import { Column, ColumnBody, ColumnCount, ColumnHeader, EmptyColumn, } from "./styles";
9
- import { CARD_GAP } from "./constants";
10
9
  import { MemoizedSortableVirtualRow } from "./virtualRow";
11
- export const KanbanColumn = ({ columnToken, groupValue, groupLabel, renderColumnHeader, itemTokens, cardByToken, cardIdByToken, groupByToken, renderCard, onMeasure, rowHeights, listRefMap, columnWidth, columnHeight, estimatedCardHeight, overscanCount, emptyColumnPlaceholder, }) => {
10
+ export const KanbanColumn = ({ columnToken, groupValue, groupLabel, renderColumnHeader, itemTokens, cardByToken, cardIdByToken, groupByToken, renderCard, onMeasure, rowHeights, listRefMap, columnWidth, columnHeight, cardGap, estimatedCardHeight, overscanCount, emptyColumnPlaceholder, }) => {
12
11
  const { setNodeRef, isOver } = useDroppable({
13
12
  id: columnToken,
14
13
  data: {
@@ -24,6 +23,7 @@ export const KanbanColumn = ({ columnToken, groupValue, groupLabel, renderColumn
24
23
  groupByToken,
25
24
  renderCard,
26
25
  onMeasure,
26
+ cardGap,
27
27
  }), [
28
28
  cardByToken,
29
29
  cardIdByToken,
@@ -32,6 +32,7 @@ export const KanbanColumn = ({ columnToken, groupValue, groupLabel, renderColumn
32
32
  itemTokens,
33
33
  onMeasure,
34
34
  renderCard,
35
+ cardGap,
35
36
  ]);
36
37
  const previousOrderTokensRef = useRef([]);
37
38
  const scrollTargetRef = useRef(null);
@@ -112,6 +113,6 @@ export const KanbanColumn = ({ columnToken, groupValue, groupLabel, renderColumn
112
113
  }, outerRef: setScrollViewportRef, width: width, height: height, itemCount: cardCount, overscanCount: overscanCount, itemData: rowData, itemKey: (index, data) => data.itemTokens[index], itemSize: (index) => {
113
114
  var _a;
114
115
  const token = rowData.itemTokens[index];
115
- return (_a = rowHeights[token]) !== null && _a !== void 0 ? _a : estimatedCardHeight + CARD_GAP;
116
+ return (_a = rowHeights[token]) !== null && _a !== void 0 ? _a : estimatedCardHeight + cardGap;
116
117
  }, children: MemoizedSortableVirtualRow })) })) }) })] }));
117
118
  };
@@ -1,5 +1,4 @@
1
1
  import { DropAnimation } from "@dnd-kit/core";
2
2
  export declare const GROUP_PREFIX = "group::";
3
3
  export declare const CARD_PREFIX = "card::";
4
- export declare const CARD_GAP = 5;
5
4
  export declare const dropAnimation: DropAnimation;
@@ -1,7 +1,6 @@
1
1
  import { defaultDropAnimationSideEffects } from "@dnd-kit/core";
2
2
  export const GROUP_PREFIX = "group::";
3
3
  export const CARD_PREFIX = "card::";
4
- export const CARD_GAP = 5;
5
4
  export const dropAnimation = {
6
5
  duration: 220,
7
6
  easing: "cubic-bezier(0.2, 0, 0, 1)",
@@ -1,4 +1,6 @@
1
- export declare const Board: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
1
+ export declare const Board: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
2
+ $columnGap?: number;
3
+ }>> & string;
2
4
  export declare const Column: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
3
5
  $width: number;
4
6
  $isOver: boolean;
@@ -2,7 +2,7 @@ import styled from "styled-components";
2
2
  export const Board = styled.div `
3
3
  display: flex;
4
4
  align-items: stretch;
5
- gap: 12px;
5
+ gap: ${({ $columnGap }) => $columnGap !== null && $columnGap !== void 0 ? $columnGap : 12}px;
6
6
  width: 100%;
7
7
  height: 100%;
8
8
  min-height: 0;
@@ -46,6 +46,8 @@ export interface KanbanProps<TCard, TGroup extends KanbanGroupKey> {
46
46
  renderColumnHeader?: (group: TGroup, cardCount: number) => ReactNode;
47
47
  /** Width of each column in pixels. Defaults to `320`. */
48
48
  columnWidth?: number;
49
+ /** Gap between columns in pixels. Defaults to `8`. */
50
+ columnGap?: number;
49
51
  /** Fixed column body height in pixels. If omitted, it grows with container layout. */
50
52
  columnHeight?: number;
51
53
  /** Fallback card height estimate for virtualization. Defaults to `100`. */
@@ -60,4 +62,6 @@ export interface KanbanProps<TCard, TGroup extends KanbanGroupKey> {
60
62
  emptyColumnPlaceholder?: ReactNode;
61
63
  /** Optional class name applied to the board root element. */
62
64
  className?: string;
65
+ /** Gap between cards in pixels. Defaults to `8`. */
66
+ cardGap?: number;
63
67
  }
@@ -8,6 +8,7 @@ export interface ColumnRowData<TCard, TGroup extends KanbanGroupKey> {
8
8
  cardIdByToken: Map<string, string>;
9
9
  groupByToken: Map<string, TGroup>;
10
10
  renderCard: (args: RenderCardArgs<TCard, TGroup>) => ReactNode;
11
+ cardGap: number;
11
12
  onMeasure: (columnToken: string, cardToken: string, index: number, height: number) => void;
12
13
  }
13
14
  export declare const MemoizedSortableVirtualRow: import("react").MemoExoticComponent<({ data, index, style, }: ListChildComponentProps<ColumnRowData<any, any>>) => import("react/jsx-runtime").JSX.Element | null>;
@@ -4,7 +4,6 @@ import { CSS } from "@dnd-kit/utilities";
4
4
  import { areEqual, } from "react-window";
5
5
  import { memo, useCallback, useEffect, useState } from "react";
6
6
  import { CardContainer } from "./styles";
7
- import { CARD_GAP } from "./constants";
8
7
  const SortableVirtualRow = ({ data, index, style, }) => {
9
8
  const cardToken = data.itemTokens[index];
10
9
  const card = data.cardByToken.get(cardToken);
@@ -36,7 +35,7 @@ const SortableVirtualRow = ({ data, index, style, }) => {
36
35
  if (!nextHeight || nextHeight <= 0) {
37
36
  return;
38
37
  }
39
- data.onMeasure(data.columnToken, cardToken, index, Math.ceil(nextHeight) + CARD_GAP);
38
+ data.onMeasure(data.columnToken, cardToken, index, Math.ceil(nextHeight) + data.cardGap);
40
39
  });
41
40
  observer.observe(contentNode);
42
41
  return () => observer.disconnect();
@@ -44,7 +43,7 @@ const SortableVirtualRow = ({ data, index, style, }) => {
44
43
  if (!card || !cardId || group == null) {
45
44
  return null;
46
45
  }
47
- return (_jsx("div", { ref: setCombinedRef, style: Object.assign(Object.assign({}, style), { transform: CSS.Transform.toString(transform), transition, willChange: isDragging || isSorting ? "transform" : undefined, zIndex: isDragging ? 2 : 1 }), children: _jsx(CardContainer, { ref: setContentNode, style: { marginBottom: CARD_GAP }, "$isDragging": isDragging, "$isDragOverlay": false, children: _jsx("div", Object.assign({}, attributes, listeners, { children: data.renderCard({
46
+ return (_jsx("div", { ref: setCombinedRef, style: Object.assign(Object.assign({}, style), { transform: CSS.Transform.toString(transform), transition, willChange: isDragging || isSorting ? "transform" : undefined, zIndex: isDragging ? 2 : 1 }), children: _jsx(CardContainer, { ref: setContentNode, style: { marginBottom: data.cardGap }, "$isDragging": isDragging, "$isDragOverlay": false, children: _jsx("div", Object.assign({}, attributes, listeners, { children: data.renderCard({
48
47
  card,
49
48
  cardId,
50
49
  group,
@@ -0,0 +1,3 @@
1
+ import { SegmentedControlProps } from "./SegmentedControl.types";
2
+ export type { SegmentedControlDataItem, SegmentedControlProps, SegmentedControlVariant, } from "./SegmentedControl.types";
3
+ export declare const SegmentedControl: React.FC<SegmentedControlProps>;
@@ -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
@@ -41,3 +41,4 @@ export * from "./Table";
41
41
  export type { ColumnProps, TableProps } from "./Table";
42
42
  export * from "./Tabs";
43
43
  export * from "./Divider";
44
+ export * from "./SegmentedControl";
package/dist/index.js CHANGED
@@ -33,3 +33,4 @@ export * from "./FileViewer";
33
33
  export * from "./Table";
34
34
  export * from "./Tabs";
35
35
  export * from "./Divider";
36
+ export * from "./SegmentedControl";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monolith-forensics/monolith-ui",
3
- "version": "1.3.112-dev.0",
3
+ "version": "1.3.112-dev.2",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "author": "Matt Danner (Monolith Forensics LLC)",
@@ -1,16 +0,0 @@
1
- export declare const ORDER_GAP = 1024;
2
- type GetOrderByCardId = (cardId: string) => number;
3
- export type OrderComputationResult = {
4
- newOrder: number;
5
- beforeCardId: string | null;
6
- afterCardId: string | null;
7
- };
8
- type ComputeOrderParams = {
9
- destinationCardIds: string[];
10
- movedCardId: string;
11
- destinationIndex: number;
12
- getOrderByCardId: GetOrderByCardId;
13
- orderGap?: number;
14
- };
15
- export declare const computeOrderForMove: ({ destinationCardIds, movedCardId, destinationIndex, getOrderByCardId, orderGap, }: ComputeOrderParams) => OrderComputationResult;
16
- export {};
@@ -1,52 +0,0 @@
1
- export const ORDER_GAP = 1024;
2
- const coerceOrderValue = (value, fallback) => {
3
- return Number.isFinite(value) ? value : fallback;
4
- };
5
- export const computeOrderForMove = ({ destinationCardIds, movedCardId, destinationIndex, getOrderByCardId, orderGap = ORDER_GAP, }) => {
6
- const total = destinationCardIds.length;
7
- if (total === 0) {
8
- return {
9
- newOrder: 0,
10
- beforeCardId: null,
11
- afterCardId: null,
12
- };
13
- }
14
- const safeIndex = Math.max(0, Math.min(destinationIndex, total - 1));
15
- const currentCardId = destinationCardIds[safeIndex];
16
- const resolvedIndex = currentCardId === movedCardId
17
- ? safeIndex
18
- : destinationCardIds.indexOf(movedCardId);
19
- const targetIndex = resolvedIndex >= 0 ? resolvedIndex : safeIndex;
20
- const beforeCardId = targetIndex > 0 ? destinationCardIds[targetIndex - 1] : null;
21
- const afterCardId = targetIndex < total - 1 ? destinationCardIds[targetIndex + 1] : null;
22
- if (beforeCardId === null && afterCardId === null) {
23
- return {
24
- newOrder: 0,
25
- beforeCardId,
26
- afterCardId,
27
- };
28
- }
29
- if (beforeCardId === null && afterCardId !== null) {
30
- const afterOrder = coerceOrderValue(getOrderByCardId(afterCardId), 0);
31
- return {
32
- newOrder: afterOrder - orderGap,
33
- beforeCardId,
34
- afterCardId,
35
- };
36
- }
37
- if (beforeCardId !== null && afterCardId === null) {
38
- const beforeOrder = coerceOrderValue(getOrderByCardId(beforeCardId), 0);
39
- return {
40
- newOrder: beforeOrder + orderGap,
41
- beforeCardId,
42
- afterCardId,
43
- };
44
- }
45
- const beforeOrder = coerceOrderValue(getOrderByCardId(beforeCardId), 0);
46
- const afterOrder = coerceOrderValue(getOrderByCardId(afterCardId), beforeOrder + orderGap);
47
- return {
48
- newOrder: (beforeOrder + afterOrder) / 2,
49
- beforeCardId,
50
- afterCardId,
51
- };
52
- };