@socprime/react-ui 0.0.9 → 0.0.11
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/domain.d.ts +26 -1
- package/index.d.ts +24 -1
- package/package.json +1 -1
- package/ui/Aside/AsideHorizontal.js +39 -0
- package/ui/Aside/index.js +3 -1
- package/ui/AttackTimeline/AttackTimeline.js +89 -0
- package/ui/AttackTimeline/CyberpunkCorner.js +28 -0
- package/ui/AttackTimeline/TimelineBranchNode.js +46 -0
- package/ui/AttackTimeline/TimelineBranches.js +99 -0
- package/ui/AttackTimeline/TimelineDividers.js +36 -0
- package/ui/AttackTimeline/TimelineSegments.js +39 -0
- package/ui/AttackTimeline/TimelineStepItem.js +42 -0
- package/ui/AttackTimeline/TimelineStepTooltip.js +31 -0
- package/ui/AttackTimeline/branchGeometry.js +65 -0
- package/ui/AttackTimeline/index.js +6 -0
- package/ui/AttackTimeline/mapChainGraphBranches.js +29 -0
- package/ui/AttackTimeline/timelineConstants.js +30 -0
- package/ui/AttackTimeline/types.js +0 -0
- package/ui/AttackTimeline/useTimeline.js +48 -0
- package/ui/MobileNav/MobileNav.js +111 -0
- package/ui/MobileNav/index.js +4 -0
- package/ui/Select/SelectDefault.js +7 -1
- package/ui/entries/domain.js +1 -0
- package/ui/index.js +1 -0
package/domain.d.ts
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import * as React$1 from 'react';
|
|
2
2
|
import { ComponentProps } from 'react';
|
|
3
3
|
|
|
4
|
+
interface AttackTimelineStep {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
hit: boolean;
|
|
8
|
+
code: string;
|
|
9
|
+
}
|
|
10
|
+
interface ChainGraphBranch {
|
|
11
|
+
from_code: string;
|
|
12
|
+
to_code: string;
|
|
13
|
+
node: AttackTimelineStep;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type ChainGraphBranchView = {
|
|
17
|
+
fromIndex: number;
|
|
18
|
+
toIndex: number;
|
|
19
|
+
step: ChainGraphBranch['node'];
|
|
20
|
+
};
|
|
21
|
+
declare const mapChainGraphBranches: (branches: ChainGraphBranch[] | undefined, chainGraph: AttackTimelineStep[]) => ChainGraphBranchView[];
|
|
22
|
+
|
|
23
|
+
interface AttackTimelineProps {
|
|
24
|
+
steps?: AttackTimelineStep[];
|
|
25
|
+
branches?: ChainGraphBranchView[];
|
|
26
|
+
}
|
|
27
|
+
declare const AttackTimeline: React.FC<AttackTimelineProps>;
|
|
28
|
+
|
|
4
29
|
interface CorrelationTimerProps {
|
|
5
30
|
isRunning: boolean;
|
|
6
31
|
runningTime?: number | null;
|
|
@@ -20,4 +45,4 @@ declare const SyncProcessBar: () => React$1.JSX.Element;
|
|
|
20
45
|
|
|
21
46
|
type SyncProcessBarProps = ComponentProps<typeof SyncProcessBar>;
|
|
22
47
|
|
|
23
|
-
export { CorrelationTimer, type CorrelationTimerProps, Severity, type SeverityProps, SyncProcessBar, type SyncProcessBarProps, type TSeverity };
|
|
48
|
+
export { AttackTimeline, type AttackTimelineProps, type AttackTimelineStep, type ChainGraphBranch, type ChainGraphBranchView, CorrelationTimer, type CorrelationTimerProps, Severity, type SeverityProps, SyncProcessBar, type SyncProcessBarProps, type TSeverity, mapChainGraphBranches };
|
package/index.d.ts
CHANGED
|
@@ -74,6 +74,12 @@ interface AsideProps {
|
|
|
74
74
|
}
|
|
75
75
|
declare const Aside: React.FC<AsideProps>;
|
|
76
76
|
|
|
77
|
+
interface AsideHorizontalProps {
|
|
78
|
+
tabs: AsideTab[];
|
|
79
|
+
className?: string;
|
|
80
|
+
}
|
|
81
|
+
declare const AsideHorizontal: React.FC<AsideHorizontalProps>;
|
|
82
|
+
|
|
77
83
|
declare const badgeVariants: (props?: ({
|
|
78
84
|
variant?: "secondary" | "outline" | "yellow" | "purple" | "default" | "dark" | "defaultOutline" | "warning" | "critical" | "success" | "lightBlue" | "redViolet" | null | undefined;
|
|
79
85
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
@@ -377,6 +383,22 @@ interface LabelInfoProps extends React.ComponentProps<typeof LabelPrimitive.Root
|
|
|
377
383
|
}
|
|
378
384
|
declare const LabelInfo: React.FC<LabelInfoProps>;
|
|
379
385
|
|
|
386
|
+
interface MobileNavItem {
|
|
387
|
+
id: string;
|
|
388
|
+
label: string;
|
|
389
|
+
icon: React.FC<{
|
|
390
|
+
className?: string;
|
|
391
|
+
}>;
|
|
392
|
+
to?: string;
|
|
393
|
+
}
|
|
394
|
+
interface MobileNavProps {
|
|
395
|
+
items: MobileNavItem[];
|
|
396
|
+
logo?: React.ReactNode;
|
|
397
|
+
title?: string;
|
|
398
|
+
className?: string;
|
|
399
|
+
}
|
|
400
|
+
declare const MobileNav: React.FC<MobileNavProps>;
|
|
401
|
+
|
|
380
402
|
interface MultiSelectOption {
|
|
381
403
|
label: string;
|
|
382
404
|
value: string;
|
|
@@ -509,6 +531,7 @@ interface SelectDefaultProps {
|
|
|
509
531
|
loading?: boolean;
|
|
510
532
|
disabled?: boolean;
|
|
511
533
|
style?: React.CSSProperties;
|
|
534
|
+
icon?: React.ReactNode;
|
|
512
535
|
renderItemLabel?: (value: string) => React.ReactNode;
|
|
513
536
|
renderTriggerLabel?: (value: string) => React.ReactNode;
|
|
514
537
|
onChange?: (value: string) => void;
|
|
@@ -712,4 +735,4 @@ declare function TreeRow({ className, children, ...props }: ComponentProps<'div'
|
|
|
712
735
|
type TreeGuideProps = ComponentProps<typeof TreeGuide>;
|
|
713
736
|
type TreeRowProps = ComponentProps<typeof TreeRow>;
|
|
714
737
|
|
|
715
|
-
export { Accordion, type AccordionChevronVariant, AccordionContent, type AccordionContentProps, AccordionItem, type AccordionItemProps, type AccordionProps, AccordionSection, type AccordionSectionProps, AccordionTrigger, type AccordionTriggerProps, AnimatedDots, type AnimatedDotsProps, Aside, type AsideProps, type AsideTab, Badge, type BadgeProps, Button, type ButtonProps, Checkbox, type CheckboxProps, ConditionalContent, type ConditionalContentProps, ConfirmDeleteDialog, type ConfirmDeleteDialogProps, ConfirmDeleteDialog as ConfirmDialog, DefaultDialog, type DefaultDialogProps, DeleteButtonConfirm, type DeleteButtonConfirmProps, Dialog, DialogClose, type DialogCloseProps, DialogContent, type DialogContentProps, DialogDescription, type DialogDescriptionProps, DialogFooter, type DialogFooterProps, DialogHeader, type DialogHeaderProps, DialogOverlay, type DialogOverlayProps, DialogPortal, type DialogPortalProps, type DialogProps, DialogTitle, type DialogTitleProps, DialogTrigger, type DialogTriggerProps, DropdownMenu, DropdownMenuCheckboxItem, type DropdownMenuCheckboxItemProps, DropdownMenuContent, type DropdownMenuContentProps, DropdownMenuGroup, type DropdownMenuGroupProps, DropdownMenuItem, type DropdownMenuItemProps, DropdownMenuLabel, type DropdownMenuLabelProps, DropdownMenuPortal, type DropdownMenuPortalProps, type DropdownMenuProps, DropdownMenuRadioGroup, type DropdownMenuRadioGroupProps, DropdownMenuRadioItem, type DropdownMenuRadioItemProps, DropdownMenuSeparator, type DropdownMenuSeparatorProps, DropdownMenuShortcut, type DropdownMenuShortcutProps, DropdownMenuSub, DropdownMenuSubContent, type DropdownMenuSubContentProps, type DropdownMenuSubProps, DropdownMenuSubTrigger, type DropdownMenuSubTriggerProps, DropdownMenuTrigger, type DropdownMenuTriggerProps, DropdownsCheckbox, type DropdownsCheckboxOption, type DropdownsCheckboxProps, DropdownsSelect, type DropdownsSelectOption, type DropdownsSelectProps, EmptyState, type EmptyStateProps, Field, FieldContent, type FieldContentProps, FieldDescription, type FieldDescriptionProps, FieldError, type FieldErrorProps, FieldGroup, type FieldGroupProps, FieldLabel, type FieldLabelProps, FieldLegend, type FieldLegendProps, type FieldProps, FieldSeparator, type FieldSeparatorProps, FieldSet, type FieldSetProps, FieldTitle, type FieldTitleProps, FolderTreeExpandTrigger, type FolderTreeExpandTriggerProps, GroupHeaderRow, type GroupHeaderRowProps, HelperText, type HelperTextProps, Input, InputNumber, type InputNumberProps, InputPassword, type InputPasswordProps, type InputProps, Label, LabelInfo, type LabelInfoProps, type LabelProps, MultiSelect, type MultiSelectOption, type MultiSelectProps, PageHeader, type PageHeaderProps, Pagination, PaginationContent, type PaginationContentProps, PaginationEllipsis, type PaginationEllipsisProps, PaginationItem, type PaginationItemProps, PaginationLink, type PaginationLinkProps, PaginationNext, type PaginationNextProps, PaginationPrevious, type PaginationPreviousProps, type PaginationProps, PaginationWrap, type PaginationWrapProps, Progress, type ProgressProps, RadioGroup, RadioGroupItem, type RadioGroupItemProps, type RadioGroupProps, ScrollArea, type ScrollAreaOrientation, type ScrollAreaProps, ScrollBar, SearchInput, SearchInputFields, type SearchInputFieldsProps, type SearchInputProps, Select, SelectContent, type SelectContentProps, SelectDefault, type SelectDefaultProps, SelectGroup, type SelectGroupProps, SelectItem, type SelectItemProps, SelectLabel, type SelectLabelProps, type SelectProps, SelectScrollDownButton, type SelectScrollDownButtonProps, SelectScrollUpButton, type SelectScrollUpButtonProps, SelectSeparator, type SelectSeparatorProps, SelectTrigger, type SelectTriggerProps, SelectValue, type SelectValueProps, Separator, type SeparatorProps, ServerGroupedTableBody, type ServerGroupedTableBodyProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Slider, type SliderProps, SortableHeader, type SortableHeaderProps, Spinner, SpinnerCircle, type SpinnerCircleProps, SpinnerCustom, type SpinnerProps, SpinnerSquare, type SpinnerSquareProps, type SuggestionOption, Suggestions, type SuggestionsProps, Switch, type SwitchProps, type TabItem, Table, TableBody, type TableBodyProps, TableCaption, type TableCaptionProps, TableCell, type TableCellProps, TableFooter, type TableFooterProps, TableHead, type TableHeadProps, TableHeader, type TableHeaderProps, type TableProps, TableRow, type TableRowProps, TableWrap, type TableWrapProps, Tabs, type TabsProps, TextTruncate, type TextTruncateProps, Textarea, type TextareaProps, ThemeToggle, type ThemeToggleProps, Toaster, Tooltip, TooltipContent, type TooltipContentProps, TooltipParent, type TooltipParentProps, type TooltipProps, TooltipProvider, type TooltipProviderProps, TooltipTrigger, type TooltipTriggerProps, TreeGuide, type TreeGuideProps, TreeRow, type TreeRowProps, badgeVariants, buttonVariants };
|
|
738
|
+
export { Accordion, type AccordionChevronVariant, AccordionContent, type AccordionContentProps, AccordionItem, type AccordionItemProps, type AccordionProps, AccordionSection, type AccordionSectionProps, AccordionTrigger, type AccordionTriggerProps, AnimatedDots, type AnimatedDotsProps, Aside, AsideHorizontal, type AsideHorizontalProps, type AsideProps, type AsideTab, Badge, type BadgeProps, Button, type ButtonProps, Checkbox, type CheckboxProps, ConditionalContent, type ConditionalContentProps, ConfirmDeleteDialog, type ConfirmDeleteDialogProps, ConfirmDeleteDialog as ConfirmDialog, DefaultDialog, type DefaultDialogProps, DeleteButtonConfirm, type DeleteButtonConfirmProps, Dialog, DialogClose, type DialogCloseProps, DialogContent, type DialogContentProps, DialogDescription, type DialogDescriptionProps, DialogFooter, type DialogFooterProps, DialogHeader, type DialogHeaderProps, DialogOverlay, type DialogOverlayProps, DialogPortal, type DialogPortalProps, type DialogProps, DialogTitle, type DialogTitleProps, DialogTrigger, type DialogTriggerProps, DropdownMenu, DropdownMenuCheckboxItem, type DropdownMenuCheckboxItemProps, DropdownMenuContent, type DropdownMenuContentProps, DropdownMenuGroup, type DropdownMenuGroupProps, DropdownMenuItem, type DropdownMenuItemProps, DropdownMenuLabel, type DropdownMenuLabelProps, DropdownMenuPortal, type DropdownMenuPortalProps, type DropdownMenuProps, DropdownMenuRadioGroup, type DropdownMenuRadioGroupProps, DropdownMenuRadioItem, type DropdownMenuRadioItemProps, DropdownMenuSeparator, type DropdownMenuSeparatorProps, DropdownMenuShortcut, type DropdownMenuShortcutProps, DropdownMenuSub, DropdownMenuSubContent, type DropdownMenuSubContentProps, type DropdownMenuSubProps, DropdownMenuSubTrigger, type DropdownMenuSubTriggerProps, DropdownMenuTrigger, type DropdownMenuTriggerProps, DropdownsCheckbox, type DropdownsCheckboxOption, type DropdownsCheckboxProps, DropdownsSelect, type DropdownsSelectOption, type DropdownsSelectProps, EmptyState, type EmptyStateProps, Field, FieldContent, type FieldContentProps, FieldDescription, type FieldDescriptionProps, FieldError, type FieldErrorProps, FieldGroup, type FieldGroupProps, FieldLabel, type FieldLabelProps, FieldLegend, type FieldLegendProps, type FieldProps, FieldSeparator, type FieldSeparatorProps, FieldSet, type FieldSetProps, FieldTitle, type FieldTitleProps, FolderTreeExpandTrigger, type FolderTreeExpandTriggerProps, GroupHeaderRow, type GroupHeaderRowProps, HelperText, type HelperTextProps, Input, InputNumber, type InputNumberProps, InputPassword, type InputPasswordProps, type InputProps, Label, LabelInfo, type LabelInfoProps, type LabelProps, MobileNav, type MobileNavItem, type MobileNavProps, MultiSelect, type MultiSelectOption, type MultiSelectProps, PageHeader, type PageHeaderProps, Pagination, PaginationContent, type PaginationContentProps, PaginationEllipsis, type PaginationEllipsisProps, PaginationItem, type PaginationItemProps, PaginationLink, type PaginationLinkProps, PaginationNext, type PaginationNextProps, PaginationPrevious, type PaginationPreviousProps, type PaginationProps, PaginationWrap, type PaginationWrapProps, Progress, type ProgressProps, RadioGroup, RadioGroupItem, type RadioGroupItemProps, type RadioGroupProps, ScrollArea, type ScrollAreaOrientation, type ScrollAreaProps, ScrollBar, SearchInput, SearchInputFields, type SearchInputFieldsProps, type SearchInputProps, Select, SelectContent, type SelectContentProps, SelectDefault, type SelectDefaultProps, SelectGroup, type SelectGroupProps, SelectItem, type SelectItemProps, SelectLabel, type SelectLabelProps, type SelectProps, SelectScrollDownButton, type SelectScrollDownButtonProps, SelectScrollUpButton, type SelectScrollUpButtonProps, SelectSeparator, type SelectSeparatorProps, SelectTrigger, type SelectTriggerProps, SelectValue, type SelectValueProps, Separator, type SeparatorProps, ServerGroupedTableBody, type ServerGroupedTableBodyProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Slider, type SliderProps, SortableHeader, type SortableHeaderProps, Spinner, SpinnerCircle, type SpinnerCircleProps, SpinnerCustom, type SpinnerProps, SpinnerSquare, type SpinnerSquareProps, type SuggestionOption, Suggestions, type SuggestionsProps, Switch, type SwitchProps, type TabItem, Table, TableBody, type TableBodyProps, TableCaption, type TableCaptionProps, TableCell, type TableCellProps, TableFooter, type TableFooterProps, TableHead, type TableHeadProps, TableHeader, type TableHeaderProps, type TableProps, TableRow, type TableRowProps, TableWrap, type TableWrapProps, Tabs, type TabsProps, TextTruncate, type TextTruncateProps, Textarea, type TextareaProps, ThemeToggle, type ThemeToggleProps, Toaster, Tooltip, TooltipContent, type TooltipContentProps, TooltipParent, type TooltipParentProps, type TooltipProps, TooltipProvider, type TooltipProviderProps, TooltipTrigger, type TooltipTriggerProps, TreeGuide, type TreeGuideProps, TreeRow, type TreeRowProps, badgeVariants, buttonVariants };
|
package/package.json
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { Link, useLocation } from "react-router-dom";
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
const AsideHorizontal = ({ tabs, className }) => {
|
|
6
|
+
const { pathname } = useLocation();
|
|
7
|
+
const activeRef = useRef(null);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
activeRef.current?.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" });
|
|
10
|
+
}, [pathname]);
|
|
11
|
+
const links = tabs.filter(({ title }) => !title);
|
|
12
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
13
|
+
/* @__PURE__ */ jsx("div", { className: "flex snap-x gap-2 overflow-x-auto pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", children: links.map(({ id, label, icon, to }) => {
|
|
14
|
+
const Icon = icon || void 0;
|
|
15
|
+
const isActive = pathname.startsWith(id);
|
|
16
|
+
return /* @__PURE__ */ jsxs(
|
|
17
|
+
Link,
|
|
18
|
+
{
|
|
19
|
+
ref: isActive ? activeRef : void 0,
|
|
20
|
+
to: to ?? id,
|
|
21
|
+
className: cn(
|
|
22
|
+
"flex shrink-0 snap-start items-center gap-2 rounded-full border px-4 py-2 text-xs font-medium whitespace-nowrap transition-colors",
|
|
23
|
+
isActive ? "border-btn-primary/50 bg-btn-primary/10 text-foreground [&_svg]:text-btn-primary" : "border-border text-subdued hover:bg-hover hover:text-foreground"
|
|
24
|
+
),
|
|
25
|
+
children: [
|
|
26
|
+
Icon && /* @__PURE__ */ jsx(Icon, { className: "size-4" }),
|
|
27
|
+
label
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
id
|
|
31
|
+
);
|
|
32
|
+
}) }),
|
|
33
|
+
/* @__PURE__ */ jsx("div", { className: "from-primary pointer-events-none absolute inset-y-0 left-0 w-6 bg-gradient-to-r to-transparent" }),
|
|
34
|
+
/* @__PURE__ */ jsx("div", { className: "from-primary pointer-events-none absolute inset-y-0 right-0 w-6 bg-gradient-to-l to-transparent" })
|
|
35
|
+
] });
|
|
36
|
+
};
|
|
37
|
+
export {
|
|
38
|
+
AsideHorizontal
|
|
39
|
+
};
|
package/ui/Aside/index.js
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
3
|
+
import { CyberpunkCorner } from "./CyberpunkCorner.js";
|
|
4
|
+
import { TimelineBranches } from "./TimelineBranches.js";
|
|
5
|
+
import { BRANCH_LAYOUT_PADDING_TOP } from "./timelineConstants.js";
|
|
6
|
+
import { TimelineDividers } from "./TimelineDividers.js";
|
|
7
|
+
import { TimelineSegments } from "./TimelineSegments.js";
|
|
8
|
+
import { TimelineStepItem } from "./TimelineStepItem.js";
|
|
9
|
+
import { useTimeline } from "./useTimeline.js";
|
|
10
|
+
const AttackTimeline = ({ steps, branches = [] }) => {
|
|
11
|
+
const timelineSteps = steps ?? [];
|
|
12
|
+
const lastHitIndex = useMemo(
|
|
13
|
+
() => timelineSteps.reduce((last, step, index) => step.hit ? index : last, -1),
|
|
14
|
+
[timelineSteps]
|
|
15
|
+
);
|
|
16
|
+
const {
|
|
17
|
+
containerRef,
|
|
18
|
+
hoveredIndex,
|
|
19
|
+
hoveredBranchIndex,
|
|
20
|
+
setHoveredIndex,
|
|
21
|
+
setHoveredBranchIndex,
|
|
22
|
+
mouseX,
|
|
23
|
+
timelineWidth,
|
|
24
|
+
handleMouseMove,
|
|
25
|
+
handleMouseLeave
|
|
26
|
+
} = useTimeline();
|
|
27
|
+
const makeHoverHandlers = useCallback(
|
|
28
|
+
(index) => ({
|
|
29
|
+
onMouseEnter: () => setHoveredIndex(index),
|
|
30
|
+
onMouseLeave: () => setHoveredIndex(null)
|
|
31
|
+
}),
|
|
32
|
+
[setHoveredIndex]
|
|
33
|
+
);
|
|
34
|
+
if (timelineSteps.length === 0) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative p-[12px]", children: [
|
|
38
|
+
/* @__PURE__ */ jsx(CyberpunkCorner, { position: "top-left" }),
|
|
39
|
+
/* @__PURE__ */ jsx(CyberpunkCorner, { position: "top-right" }),
|
|
40
|
+
/* @__PURE__ */ jsx(CyberpunkCorner, { position: "bottom-left" }),
|
|
41
|
+
/* @__PURE__ */ jsx(CyberpunkCorner, { position: "bottom-right" }),
|
|
42
|
+
/* @__PURE__ */ jsx("div", { className: "mb-4 flex items-center gap-2" }),
|
|
43
|
+
/* @__PURE__ */ jsx(
|
|
44
|
+
"div",
|
|
45
|
+
{
|
|
46
|
+
className: "overflow-visible",
|
|
47
|
+
style: { paddingTop: branches.length > 0 ? BRANCH_LAYOUT_PADDING_TOP : void 0 },
|
|
48
|
+
children: /* @__PURE__ */ jsxs(
|
|
49
|
+
"div",
|
|
50
|
+
{
|
|
51
|
+
ref: containerRef,
|
|
52
|
+
className: "relative flex w-full items-start justify-between pt-2.5",
|
|
53
|
+
onMouseMove: handleMouseMove,
|
|
54
|
+
onMouseLeave: handleMouseLeave,
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ jsx(TimelineSegments, { steps: timelineSteps }),
|
|
57
|
+
/* @__PURE__ */ jsx(TimelineDividers, { mouseX, timelineWidth }),
|
|
58
|
+
/* @__PURE__ */ jsx(
|
|
59
|
+
TimelineBranches,
|
|
60
|
+
{
|
|
61
|
+
branches,
|
|
62
|
+
totalSteps: timelineSteps.length,
|
|
63
|
+
hoveredBranchIndex,
|
|
64
|
+
onBranchMouseEnter: setHoveredBranchIndex,
|
|
65
|
+
onBranchMouseLeave: () => setHoveredBranchIndex(null)
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
timelineSteps.map((step, index) => /* @__PURE__ */ jsx(
|
|
69
|
+
TimelineStepItem,
|
|
70
|
+
{
|
|
71
|
+
step,
|
|
72
|
+
index,
|
|
73
|
+
totalSteps: timelineSteps.length,
|
|
74
|
+
isCurrent: index === lastHitIndex && step.hit,
|
|
75
|
+
isHovered: hoveredIndex === index,
|
|
76
|
+
...makeHoverHandlers(index)
|
|
77
|
+
},
|
|
78
|
+
`${step.code}-${index}`
|
|
79
|
+
))
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
] });
|
|
86
|
+
};
|
|
87
|
+
export {
|
|
88
|
+
AttackTimeline
|
|
89
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
const GLOW_SHADOW = "0 0 8px rgba(74, 193, 142, 0.6)";
|
|
3
|
+
const COLOR = "#4ac18e";
|
|
4
|
+
const CyberpunkCorner = ({ position }) => {
|
|
5
|
+
const isTop = position.startsWith("top");
|
|
6
|
+
const isLeft = position.endsWith("left");
|
|
7
|
+
const edge = isTop ? "top-0" : "bottom-0";
|
|
8
|
+
const side = isLeft ? "left-0" : "right-0";
|
|
9
|
+
return /* @__PURE__ */ jsxs("div", { className: `pointer-events-none absolute ${edge} ${side} h-3 w-3`, children: [
|
|
10
|
+
/* @__PURE__ */ jsx(
|
|
11
|
+
"div",
|
|
12
|
+
{
|
|
13
|
+
className: `absolute ${edge} ${side} h-[1px] w-3`,
|
|
14
|
+
style: { backgroundColor: COLOR, boxShadow: GLOW_SHADOW }
|
|
15
|
+
}
|
|
16
|
+
),
|
|
17
|
+
/* @__PURE__ */ jsx(
|
|
18
|
+
"div",
|
|
19
|
+
{
|
|
20
|
+
className: `absolute ${edge} ${side} h-3 w-[1px]`,
|
|
21
|
+
style: { backgroundColor: COLOR, boxShadow: GLOW_SHADOW }
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
] });
|
|
25
|
+
};
|
|
26
|
+
export {
|
|
27
|
+
CyberpunkCorner
|
|
28
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from "react";
|
|
3
|
+
import { cn } from "../../lib/cn.js";
|
|
4
|
+
import { TimelineStepTooltip } from "./TimelineStepTooltip.js";
|
|
5
|
+
import { DETECTION_BYPASS_LABEL, TIMELINE_BRANCH_ORANGE_START } from "./timelineConstants.js";
|
|
6
|
+
const TimelineBranchNode = memo(
|
|
7
|
+
({ step, leftPercent, topPx, isHovered, onMouseEnter, onMouseLeave }) => {
|
|
8
|
+
const markerBoxShadow = isHovered ? `0 0 0 4px #252838, 0 0 16px color-mix(in srgb, ${TIMELINE_BRANCH_ORANGE_START} 60%, transparent)` : "0 0 0 4px #252838";
|
|
9
|
+
return /* @__PURE__ */ jsxs(
|
|
10
|
+
"div",
|
|
11
|
+
{
|
|
12
|
+
className: "pointer-events-auto absolute z-[2] flex -translate-x-1/2 flex-col items-center text-center",
|
|
13
|
+
style: { left: `${leftPercent}%`, top: `${topPx}px` },
|
|
14
|
+
onMouseEnter,
|
|
15
|
+
onMouseLeave,
|
|
16
|
+
children: [
|
|
17
|
+
/* @__PURE__ */ jsx(TimelineStepTooltip, { step, codeClassName: "text-california", className: "max-w-200", children: /* @__PURE__ */ jsx("div", { className: "relative mb-4", children: /* @__PURE__ */ jsx(
|
|
18
|
+
"div",
|
|
19
|
+
{
|
|
20
|
+
className: cn(
|
|
21
|
+
"from-california to-international-orange flex h-6 w-6 rotate-45 cursor-pointer bg-gradient-to-b p-[1.5px] transition-all duration-300",
|
|
22
|
+
isHovered && "scale-120"
|
|
23
|
+
),
|
|
24
|
+
style: { boxShadow: markerBoxShadow },
|
|
25
|
+
children: /* @__PURE__ */ jsx(
|
|
26
|
+
"div",
|
|
27
|
+
{
|
|
28
|
+
className: cn(
|
|
29
|
+
"relative h-full w-full bg-[#2b2419]",
|
|
30
|
+
isHovered && "from-california/25 to-international-orange/25 bg-gradient-to-b"
|
|
31
|
+
),
|
|
32
|
+
children: /* @__PURE__ */ jsx("div", { className: "from-california to-international-orange absolute top-1/2 left-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 bg-gradient-to-b" })
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
) }) }),
|
|
37
|
+
/* @__PURE__ */ jsx("span", { className: "text-default max-w-[120px] cursor-pointer text-[11px] font-medium", children: DETECTION_BYPASS_LABEL })
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
TimelineBranchNode.displayName = "TimelineBranchNode";
|
|
44
|
+
export {
|
|
45
|
+
TimelineBranchNode
|
|
46
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useId } from "react";
|
|
3
|
+
import { getBranchGeometry } from "./branchGeometry.js";
|
|
4
|
+
import { TimelineBranchNode } from "./TimelineBranchNode.js";
|
|
5
|
+
import {
|
|
6
|
+
BRANCH_AREA_HEIGHT,
|
|
7
|
+
BRANCH_NODE_OFFSET_Y,
|
|
8
|
+
BRANCH_OFFSET_TOP,
|
|
9
|
+
BRANCH_TOP_Y,
|
|
10
|
+
TIMELINE_BRANCH_ORANGE_END,
|
|
11
|
+
TIMELINE_BRANCH_ORANGE_START,
|
|
12
|
+
TIMELINE_GREEN
|
|
13
|
+
} from "./timelineConstants.js";
|
|
14
|
+
const TimelineBranches = memo(
|
|
15
|
+
({ branches, totalSteps, hoveredBranchIndex, onBranchMouseEnter, onBranchMouseLeave }) => {
|
|
16
|
+
const baseId = useId();
|
|
17
|
+
if (branches.length === 0) return null;
|
|
18
|
+
return /* @__PURE__ */ jsxs(
|
|
19
|
+
"div",
|
|
20
|
+
{
|
|
21
|
+
className: "pointer-events-none absolute inset-x-0 overflow-visible",
|
|
22
|
+
style: { top: BRANCH_OFFSET_TOP, height: BRANCH_AREA_HEIGHT, zIndex: 0 },
|
|
23
|
+
children: [
|
|
24
|
+
/* @__PURE__ */ jsxs(
|
|
25
|
+
"svg",
|
|
26
|
+
{
|
|
27
|
+
className: "absolute inset-0 h-full w-full overflow-visible",
|
|
28
|
+
viewBox: `0 0 100 ${BRANCH_AREA_HEIGHT}`,
|
|
29
|
+
preserveAspectRatio: "none",
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ jsx("defs", { children: branches.map((branch, index) => {
|
|
32
|
+
const { fromXPercent, toXPercent } = getBranchGeometry(
|
|
33
|
+
branch.fromIndex,
|
|
34
|
+
branch.toIndex,
|
|
35
|
+
totalSteps
|
|
36
|
+
);
|
|
37
|
+
return /* @__PURE__ */ jsxs(
|
|
38
|
+
"linearGradient",
|
|
39
|
+
{
|
|
40
|
+
id: `${baseId}-branch-gradient-${index}`,
|
|
41
|
+
gradientUnits: "userSpaceOnUse",
|
|
42
|
+
x1: fromXPercent,
|
|
43
|
+
y1: 0,
|
|
44
|
+
x2: toXPercent,
|
|
45
|
+
y2: 0,
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: TIMELINE_GREEN }),
|
|
48
|
+
/* @__PURE__ */ jsx("stop", { offset: "20%", style: { stopColor: TIMELINE_BRANCH_ORANGE_START } }),
|
|
49
|
+
/* @__PURE__ */ jsx("stop", { offset: "80%", style: { stopColor: TIMELINE_BRANCH_ORANGE_END } }),
|
|
50
|
+
/* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: TIMELINE_GREEN })
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
`gradient-${index}`
|
|
54
|
+
);
|
|
55
|
+
}) }),
|
|
56
|
+
branches.map((branch, index) => {
|
|
57
|
+
const geometry = getBranchGeometry(branch.fromIndex, branch.toIndex, totalSteps);
|
|
58
|
+
const gradientId = `${baseId}-branch-gradient-${index}`;
|
|
59
|
+
return /* @__PURE__ */ jsx(
|
|
60
|
+
"path",
|
|
61
|
+
{
|
|
62
|
+
d: geometry.outlinePath,
|
|
63
|
+
fill: "none",
|
|
64
|
+
stroke: `url(#${gradientId})`,
|
|
65
|
+
strokeWidth: 1,
|
|
66
|
+
strokeLinecap: "round",
|
|
67
|
+
strokeLinejoin: "round",
|
|
68
|
+
vectorEffect: "non-scaling-stroke"
|
|
69
|
+
},
|
|
70
|
+
`branch-shape-${index}`
|
|
71
|
+
);
|
|
72
|
+
})
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
),
|
|
76
|
+
branches.map((branch, index) => {
|
|
77
|
+
const { nodeXPercent } = getBranchGeometry(branch.fromIndex, branch.toIndex, totalSteps);
|
|
78
|
+
return /* @__PURE__ */ jsx(
|
|
79
|
+
TimelineBranchNode,
|
|
80
|
+
{
|
|
81
|
+
step: branch.step,
|
|
82
|
+
leftPercent: nodeXPercent,
|
|
83
|
+
topPx: BRANCH_TOP_Y + BRANCH_NODE_OFFSET_Y,
|
|
84
|
+
isHovered: hoveredBranchIndex === index,
|
|
85
|
+
onMouseEnter: () => onBranchMouseEnter(index),
|
|
86
|
+
onMouseLeave: onBranchMouseLeave
|
|
87
|
+
},
|
|
88
|
+
`branch-node-${index}`
|
|
89
|
+
);
|
|
90
|
+
})
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
TimelineBranches.displayName = "TimelineBranches";
|
|
97
|
+
export {
|
|
98
|
+
TimelineBranches
|
|
99
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useMemo } from "react";
|
|
3
|
+
import { calculateLineHeight } from "./useTimeline.js";
|
|
4
|
+
const TOTAL_MARKS = Math.floor(100 / 0.4);
|
|
5
|
+
const TimelineDividers = memo(
|
|
6
|
+
({ mouseX, timelineWidth }) => {
|
|
7
|
+
const marks = useMemo(
|
|
8
|
+
() => Array.from({ length: TOTAL_MARKS }, (_, i) => {
|
|
9
|
+
const leftPosition = i * 0.4;
|
|
10
|
+
const height = calculateLineHeight(leftPosition, mouseX, timelineWidth);
|
|
11
|
+
const opacity = i % 3 === 0 ? 0.6 : 0.4;
|
|
12
|
+
const topOffset = 16 - height / 2;
|
|
13
|
+
return { i, leftPosition, height, opacity, topOffset };
|
|
14
|
+
}),
|
|
15
|
+
[mouseX, timelineWidth]
|
|
16
|
+
);
|
|
17
|
+
return /* @__PURE__ */ jsx(Fragment, { children: marks.map(({ i, leftPosition, height, opacity, topOffset }) => /* @__PURE__ */ jsx(
|
|
18
|
+
"div",
|
|
19
|
+
{
|
|
20
|
+
className: "absolute w-[1px] bg-[#3b3d4f] transition-all duration-75",
|
|
21
|
+
style: {
|
|
22
|
+
height: `${height}px`,
|
|
23
|
+
opacity,
|
|
24
|
+
left: `${leftPosition}%`,
|
|
25
|
+
top: `${topOffset}px`,
|
|
26
|
+
zIndex: 0
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
`mark-${i}`
|
|
30
|
+
)) });
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
TimelineDividers.displayName = "TimelineDividers";
|
|
34
|
+
export {
|
|
35
|
+
TimelineDividers
|
|
36
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
const getSegmentColor = (current, next) => {
|
|
3
|
+
const currentActive = current.hit;
|
|
4
|
+
const nextActive = next.hit;
|
|
5
|
+
if (currentActive && nextActive) {
|
|
6
|
+
return "#4ac18e";
|
|
7
|
+
}
|
|
8
|
+
if (currentActive && !nextActive) {
|
|
9
|
+
return "linear-gradient(to right, #4ac18e, #3b3d4f)";
|
|
10
|
+
}
|
|
11
|
+
if (!currentActive && nextActive) {
|
|
12
|
+
return "linear-gradient(to right, #3b3d4f, #4ac18e)";
|
|
13
|
+
}
|
|
14
|
+
return "#3b3d4f";
|
|
15
|
+
};
|
|
16
|
+
const TimelineSegments = ({ steps }) => {
|
|
17
|
+
const segmentWidth = 100 / steps.length;
|
|
18
|
+
return /* @__PURE__ */ jsx(Fragment, { children: steps.map((step, index) => {
|
|
19
|
+
if (index === steps.length - 1) return null;
|
|
20
|
+
const leftPosition = index * segmentWidth + segmentWidth / 2;
|
|
21
|
+
const lineColor = getSegmentColor(step, steps[index + 1]);
|
|
22
|
+
return /* @__PURE__ */ jsx(
|
|
23
|
+
"div",
|
|
24
|
+
{
|
|
25
|
+
className: "absolute top-4 h-[1px]",
|
|
26
|
+
style: {
|
|
27
|
+
left: `${leftPosition}%`,
|
|
28
|
+
width: `${segmentWidth}%`,
|
|
29
|
+
background: lineColor,
|
|
30
|
+
zIndex: 1
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
`segment-${index}`
|
|
34
|
+
);
|
|
35
|
+
}) });
|
|
36
|
+
};
|
|
37
|
+
export {
|
|
38
|
+
TimelineSegments
|
|
39
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from "react";
|
|
3
|
+
import { TimelineStepTooltip } from "./TimelineStepTooltip.js";
|
|
4
|
+
const TimelineStepItem = memo(
|
|
5
|
+
({ step, totalSteps, isCurrent, isHovered, onMouseEnter, onMouseLeave }) => {
|
|
6
|
+
const isActive = step.hit;
|
|
7
|
+
const markerBoxShadow = isHovered ? "0 0 0 4px #252838, 0 0 16px rgba(74, 193, 142, 0.6)" : isActive ? "0 0 0 4px #252838" : "none";
|
|
8
|
+
return /* @__PURE__ */ jsxs(
|
|
9
|
+
"div",
|
|
10
|
+
{
|
|
11
|
+
className: "group relative flex flex-col items-center text-center",
|
|
12
|
+
style: { width: `${100 / totalSteps}%`, zIndex: 1 },
|
|
13
|
+
onMouseEnter,
|
|
14
|
+
onMouseLeave,
|
|
15
|
+
children: [
|
|
16
|
+
/* @__PURE__ */ jsx(TimelineStepTooltip, { step, className: "max-w-200", children: /* @__PURE__ */ jsx("div", { className: "relative mb-4", children: /* @__PURE__ */ jsx(
|
|
17
|
+
"div",
|
|
18
|
+
{
|
|
19
|
+
className: `h-[13px] w-[13px] rotate-45 transform cursor-pointer transition-all duration-300 ${isActive ? "border-success border-[1.5px] bg-[#1f2b27]" : "border-[1.5px] border-[#57586E] bg-[#252838]"} ${isHovered ? "border-success bg-success scale-150" : ""}`,
|
|
20
|
+
style: { boxShadow: markerBoxShadow },
|
|
21
|
+
children: isActive && /* @__PURE__ */ jsx("div", { className: "bg-success absolute top-1/2 left-1/2 h-1 w-1 -translate-x-1/2 -translate-y-1/2 transform" })
|
|
22
|
+
}
|
|
23
|
+
) }) }),
|
|
24
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col items-center gap-1", children: /* @__PURE__ */ jsx(
|
|
25
|
+
"span",
|
|
26
|
+
{
|
|
27
|
+
className: `cursor-pointer text-[11px] font-medium transition-colors ${isActive ? "text-default" : "text-[#8C9290]"}`,
|
|
28
|
+
style: {
|
|
29
|
+
textShadow: isCurrent ? "0 0 20px rgba(255,255,255,0.1)" : "none"
|
|
30
|
+
},
|
|
31
|
+
children: step.name
|
|
32
|
+
}
|
|
33
|
+
) })
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
TimelineStepItem.displayName = "TimelineStepItem";
|
|
40
|
+
export {
|
|
41
|
+
TimelineStepItem
|
|
42
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../lib/cn.js";
|
|
3
|
+
import { Tooltip } from "../Tooltip/index.js";
|
|
4
|
+
const TimelineStepTooltip = ({
|
|
5
|
+
step,
|
|
6
|
+
className,
|
|
7
|
+
codeClassName = "text-success",
|
|
8
|
+
children
|
|
9
|
+
}) => /* @__PURE__ */ jsx(
|
|
10
|
+
Tooltip,
|
|
11
|
+
{
|
|
12
|
+
className: cn("border-border bg-primary max-w-100 rounded-lg border p-3 shadow-xl", className),
|
|
13
|
+
classNameArrow: "bg-primary fill-primary",
|
|
14
|
+
content: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
15
|
+
/* @__PURE__ */ jsxs("div", { className: "text-default mb-1 text-xs font-medium", children: [
|
|
16
|
+
step.name,
|
|
17
|
+
" ",
|
|
18
|
+
/* @__PURE__ */ jsxs("span", { className: `text-xs ${codeClassName}`, children: [
|
|
19
|
+
"(",
|
|
20
|
+
step.code,
|
|
21
|
+
")"
|
|
22
|
+
] })
|
|
23
|
+
] }),
|
|
24
|
+
/* @__PURE__ */ jsx("div", { className: "text-subdued text-2xs break-words", children: /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: step.description ?? "" } }) })
|
|
25
|
+
] }),
|
|
26
|
+
children
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
export {
|
|
30
|
+
TimelineStepTooltip
|
|
31
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { BRANCH_BOTTOM_Y, BRANCH_TOP_Y } from "./timelineConstants.js";
|
|
2
|
+
const CHAMFER_RADIUS = 0;
|
|
3
|
+
const distance = (ax, ay, bx, by) => Math.hypot(bx - ax, by - ay);
|
|
4
|
+
const buildChamferedPath = (points, chamfer) => {
|
|
5
|
+
if (points.length < 2) return "";
|
|
6
|
+
const segments = [];
|
|
7
|
+
for (let i = 0; i < points.length; i++) {
|
|
8
|
+
const [cx, cy] = points[i];
|
|
9
|
+
const prev = points[i - 1];
|
|
10
|
+
const next = points[i + 1];
|
|
11
|
+
if (!prev || !next) {
|
|
12
|
+
if (i === 0) {
|
|
13
|
+
segments.push(`M ${cx} ${cy}`);
|
|
14
|
+
} else {
|
|
15
|
+
segments.push(`L ${cx} ${cy}`);
|
|
16
|
+
}
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const [px, py] = prev;
|
|
20
|
+
const [nx, ny] = next;
|
|
21
|
+
const inLen = distance(px, py, cx, cy);
|
|
22
|
+
const outLen = distance(cx, cy, nx, ny);
|
|
23
|
+
const radius = Math.min(chamfer, inLen * 0.35, outLen * 0.35);
|
|
24
|
+
const inX = cx + (px - cx) / inLen * radius;
|
|
25
|
+
const inY = cy + (py - cy) / inLen * radius;
|
|
26
|
+
const outX = cx + (nx - cx) / outLen * radius;
|
|
27
|
+
const outY = cy + (ny - cy) / outLen * radius;
|
|
28
|
+
if (i === 0) {
|
|
29
|
+
segments.push(`M ${inX} ${inY}`);
|
|
30
|
+
} else {
|
|
31
|
+
segments.push(`L ${inX} ${inY}`);
|
|
32
|
+
}
|
|
33
|
+
segments.push(`L ${outX} ${outY}`);
|
|
34
|
+
}
|
|
35
|
+
return segments.join(" ");
|
|
36
|
+
};
|
|
37
|
+
const getBranchGeometry = (fromIndex, toIndex, totalSteps) => {
|
|
38
|
+
const segmentWidth = 100 / totalSteps;
|
|
39
|
+
const from = Math.min(fromIndex, toIndex);
|
|
40
|
+
const to = Math.max(fromIndex, toIndex);
|
|
41
|
+
const fromXPercent = (from + 0.5) * segmentWidth;
|
|
42
|
+
const toXPercent = (to + 0.5) * segmentWidth;
|
|
43
|
+
const midXPercent = (fromXPercent + toXPercent) / 2;
|
|
44
|
+
const topHalfWidth = Math.min((toXPercent - fromXPercent) * 0.36, 26);
|
|
45
|
+
const topLeftX = midXPercent - topHalfWidth;
|
|
46
|
+
const topRightX = midXPercent + topHalfWidth;
|
|
47
|
+
const outlinePath = buildChamferedPath(
|
|
48
|
+
[
|
|
49
|
+
[fromXPercent, BRANCH_BOTTOM_Y],
|
|
50
|
+
[topLeftX, BRANCH_TOP_Y],
|
|
51
|
+
[topRightX, BRANCH_TOP_Y],
|
|
52
|
+
[toXPercent, BRANCH_BOTTOM_Y]
|
|
53
|
+
],
|
|
54
|
+
CHAMFER_RADIUS
|
|
55
|
+
);
|
|
56
|
+
return {
|
|
57
|
+
fromXPercent,
|
|
58
|
+
toXPercent,
|
|
59
|
+
nodeXPercent: midXPercent,
|
|
60
|
+
outlinePath
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
export {
|
|
64
|
+
getBranchGeometry
|
|
65
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const buildCodeToIndexMap = (chainGraph) => {
|
|
2
|
+
const codeToIndex = /* @__PURE__ */ new Map();
|
|
3
|
+
chainGraph.forEach((node, index) => {
|
|
4
|
+
if (!codeToIndex.has(node.code)) {
|
|
5
|
+
codeToIndex.set(node.code, index);
|
|
6
|
+
}
|
|
7
|
+
});
|
|
8
|
+
return codeToIndex;
|
|
9
|
+
};
|
|
10
|
+
const mapChainGraphBranches = (branches, chainGraph) => {
|
|
11
|
+
const codeToIndex = buildCodeToIndexMap(chainGraph);
|
|
12
|
+
return (branches ?? []).flatMap((branch) => {
|
|
13
|
+
const fromIndex = codeToIndex.get(branch.from_code);
|
|
14
|
+
const toIndex = codeToIndex.get(branch.to_code);
|
|
15
|
+
if (fromIndex === void 0 || toIndex === void 0 || fromIndex === toIndex) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
fromIndex,
|
|
21
|
+
toIndex,
|
|
22
|
+
step: branch.node
|
|
23
|
+
}
|
|
24
|
+
];
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
export {
|
|
28
|
+
mapChainGraphBranches
|
|
29
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const TIMELINE_GREEN = "#4ac18e";
|
|
2
|
+
const TIMELINE_INACTIVE = "#3b3d4f";
|
|
3
|
+
const TIMELINE_BRANCH_ORANGE_START = "var(--bg-california)";
|
|
4
|
+
const TIMELINE_BRANCH_ORANGE_END = "var(--bg-international-orange)";
|
|
5
|
+
const BRANCH_HEIGHT_ABOVE = 75;
|
|
6
|
+
const TIMELINE_PADDING_TOP = 10;
|
|
7
|
+
const MARKER_CENTER_Y = TIMELINE_PADDING_TOP + 6.5;
|
|
8
|
+
const BRANCH_OFFSET_TOP = MARKER_CENTER_Y - BRANCH_HEIGHT_ABOVE;
|
|
9
|
+
const BRANCH_AREA_HEIGHT = BRANCH_HEIGHT_ABOVE;
|
|
10
|
+
const BRANCH_TOP_Y = 0;
|
|
11
|
+
const BRANCH_NODE_OFFSET_Y = -12;
|
|
12
|
+
const BRANCH_BOTTOM_Y = BRANCH_HEIGHT_ABOVE;
|
|
13
|
+
const BRANCH_LAYOUT_PADDING_TOP = BRANCH_HEIGHT_ABOVE;
|
|
14
|
+
const DETECTION_BYPASS_LABEL = "Detection Bypass";
|
|
15
|
+
export {
|
|
16
|
+
BRANCH_AREA_HEIGHT,
|
|
17
|
+
BRANCH_BOTTOM_Y,
|
|
18
|
+
BRANCH_HEIGHT_ABOVE,
|
|
19
|
+
BRANCH_LAYOUT_PADDING_TOP,
|
|
20
|
+
BRANCH_NODE_OFFSET_Y,
|
|
21
|
+
BRANCH_OFFSET_TOP,
|
|
22
|
+
BRANCH_TOP_Y,
|
|
23
|
+
DETECTION_BYPASS_LABEL,
|
|
24
|
+
MARKER_CENTER_Y,
|
|
25
|
+
TIMELINE_BRANCH_ORANGE_END,
|
|
26
|
+
TIMELINE_BRANCH_ORANGE_START,
|
|
27
|
+
TIMELINE_GREEN,
|
|
28
|
+
TIMELINE_INACTIVE,
|
|
29
|
+
TIMELINE_PADDING_TOP
|
|
30
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
const calculateLineHeight = (linePositionPercent, mouseX, timelineWidth) => {
|
|
3
|
+
if (mouseX === null || timelineWidth === 0) return 24;
|
|
4
|
+
const linePositionPx = linePositionPercent / 100 * timelineWidth;
|
|
5
|
+
const distance = Math.abs(mouseX - linePositionPx);
|
|
6
|
+
const maxDistance = 80;
|
|
7
|
+
if (distance > maxDistance) return 24;
|
|
8
|
+
const factor = 1 - distance / maxDistance;
|
|
9
|
+
const maxHeight = 72;
|
|
10
|
+
return 24 + (maxHeight - 24) * factor;
|
|
11
|
+
};
|
|
12
|
+
const useTimeline = () => {
|
|
13
|
+
const containerRef = useRef(null);
|
|
14
|
+
const [hoveredIndex, setHoveredIndex] = useState(null);
|
|
15
|
+
const [hoveredBranchIndex, setHoveredBranchIndex] = useState(null);
|
|
16
|
+
const [mouseX, setMouseX] = useState(null);
|
|
17
|
+
const [timelineWidth, setTimelineWidth] = useState(0);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const element = containerRef.current;
|
|
20
|
+
if (!element) return;
|
|
21
|
+
const updateWidth = () => setTimelineWidth(element.getBoundingClientRect().width);
|
|
22
|
+
updateWidth();
|
|
23
|
+
const observer = new ResizeObserver(updateWidth);
|
|
24
|
+
observer.observe(element);
|
|
25
|
+
return () => observer.disconnect();
|
|
26
|
+
}, []);
|
|
27
|
+
const handleMouseMove = useCallback((e) => {
|
|
28
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
29
|
+
setMouseX(e.clientX - rect.left);
|
|
30
|
+
setTimelineWidth(rect.width);
|
|
31
|
+
}, []);
|
|
32
|
+
const handleMouseLeave = useCallback(() => setMouseX(null), []);
|
|
33
|
+
return {
|
|
34
|
+
containerRef,
|
|
35
|
+
mouseX,
|
|
36
|
+
hoveredIndex,
|
|
37
|
+
hoveredBranchIndex,
|
|
38
|
+
timelineWidth,
|
|
39
|
+
setHoveredIndex,
|
|
40
|
+
setHoveredBranchIndex,
|
|
41
|
+
handleMouseMove,
|
|
42
|
+
handleMouseLeave
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export {
|
|
46
|
+
calculateLineHeight,
|
|
47
|
+
useTimeline
|
|
48
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
3
|
+
import { MenuIcon, XIcon } from "lucide-react";
|
|
4
|
+
import { motion } from "motion/react";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { Link, useLocation } from "react-router-dom";
|
|
7
|
+
import { cn } from "../../lib/cn.js";
|
|
8
|
+
import { Dialog, DialogClose, DialogOverlay, DialogPortal, DialogTrigger } from "../Dialog/index.js";
|
|
9
|
+
const isPathActive = (pathname, linkTo) => pathname === linkTo || pathname.startsWith(`${linkTo}/`);
|
|
10
|
+
const MobileNav = ({ items, logo, title, className }) => {
|
|
11
|
+
const [open, setOpen] = useState(false);
|
|
12
|
+
const { pathname } = useLocation();
|
|
13
|
+
return /* @__PURE__ */ jsxs(Dialog, { open, onOpenChange: setOpen, children: [
|
|
14
|
+
/* @__PURE__ */ jsx(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
15
|
+
"button",
|
|
16
|
+
{
|
|
17
|
+
"aria-label": "Open menu",
|
|
18
|
+
className: cn(
|
|
19
|
+
"border-border text-foreground hover:bg-hover flex size-10 cursor-pointer items-center justify-center rounded-full border transition-colors lg:hidden",
|
|
20
|
+
className
|
|
21
|
+
),
|
|
22
|
+
children: /* @__PURE__ */ jsx(MenuIcon, { className: "size-5" })
|
|
23
|
+
}
|
|
24
|
+
) }),
|
|
25
|
+
/* @__PURE__ */ jsxs(DialogPortal, { children: [
|
|
26
|
+
/* @__PURE__ */ jsx(DialogOverlay, { className: "backdrop-blur-sm" }),
|
|
27
|
+
/* @__PURE__ */ jsx(DialogPrimitive.Content, { asChild: true, "aria-describedby": void 0, children: /* @__PURE__ */ jsxs(
|
|
28
|
+
motion.div,
|
|
29
|
+
{
|
|
30
|
+
className: "border-border bg-primary fixed inset-y-0 left-0 z-50 flex w-[300px] max-w-[85vw] flex-col border-r shadow-2xl",
|
|
31
|
+
initial: { x: "-100%" },
|
|
32
|
+
animate: { x: 0 },
|
|
33
|
+
transition: { type: "spring", damping: 30, stiffness: 300 },
|
|
34
|
+
children: [
|
|
35
|
+
/* @__PURE__ */ jsxs("div", { className: "border-border flex items-center justify-between border-b px-5 py-4", children: [
|
|
36
|
+
/* @__PURE__ */ jsxs(DialogPrimitive.Title, { className: "flex items-center gap-3", children: [
|
|
37
|
+
logo,
|
|
38
|
+
title && /* @__PURE__ */ jsx("span", { className: "text-lg font-semibold", children: title })
|
|
39
|
+
] }),
|
|
40
|
+
/* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
41
|
+
"button",
|
|
42
|
+
{
|
|
43
|
+
"aria-label": "Close menu",
|
|
44
|
+
className: "border-border text-foreground hover:bg-hover flex size-9 cursor-pointer items-center justify-center rounded-full border transition-colors",
|
|
45
|
+
children: /* @__PURE__ */ jsx(XIcon, { className: "size-4" })
|
|
46
|
+
}
|
|
47
|
+
) })
|
|
48
|
+
] }),
|
|
49
|
+
/* @__PURE__ */ jsx("nav", { className: "flex flex-1 flex-col gap-1 overflow-y-auto p-4", children: items.map(({ id, label, icon, to }, index) => {
|
|
50
|
+
const Icon = icon;
|
|
51
|
+
const linkTo = to ?? id;
|
|
52
|
+
const isActive = isPathActive(pathname, linkTo);
|
|
53
|
+
return /* @__PURE__ */ jsx(
|
|
54
|
+
motion.div,
|
|
55
|
+
{
|
|
56
|
+
initial: { opacity: 0, x: -24 },
|
|
57
|
+
animate: { opacity: 1, x: 0 },
|
|
58
|
+
transition: { delay: 0.08 + index * 0.05, duration: 0.25 },
|
|
59
|
+
children: /* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
60
|
+
Link,
|
|
61
|
+
{
|
|
62
|
+
to: linkTo,
|
|
63
|
+
className: cn(
|
|
64
|
+
"group relative flex w-full items-center gap-3 rounded-lg px-4 py-3 transition-colors",
|
|
65
|
+
isActive ? "bg-btn-primary/10" : "hover:bg-hover"
|
|
66
|
+
),
|
|
67
|
+
children: [
|
|
68
|
+
/* @__PURE__ */ jsx(
|
|
69
|
+
"span",
|
|
70
|
+
{
|
|
71
|
+
className: cn(
|
|
72
|
+
"bg-btn-primary absolute inset-y-2 left-0 w-1 rounded-full transition-opacity",
|
|
73
|
+
isActive ? "opacity-100" : "opacity-0"
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
),
|
|
77
|
+
/* @__PURE__ */ jsx(
|
|
78
|
+
Icon,
|
|
79
|
+
{
|
|
80
|
+
className: cn(
|
|
81
|
+
"size-5 transition-colors",
|
|
82
|
+
isActive ? "text-btn-primary" : "text-subdued group-hover:text-foreground"
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
),
|
|
86
|
+
/* @__PURE__ */ jsx(
|
|
87
|
+
"span",
|
|
88
|
+
{
|
|
89
|
+
className: cn(
|
|
90
|
+
"text-sm font-medium transition-colors",
|
|
91
|
+
isActive ? "text-foreground" : "text-subdued group-hover:text-foreground"
|
|
92
|
+
),
|
|
93
|
+
children: label
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
) })
|
|
99
|
+
},
|
|
100
|
+
id
|
|
101
|
+
);
|
|
102
|
+
}) })
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
) })
|
|
106
|
+
] })
|
|
107
|
+
] });
|
|
108
|
+
};
|
|
109
|
+
export {
|
|
110
|
+
MobileNav
|
|
111
|
+
};
|
|
@@ -18,10 +18,16 @@ const SelectDefault = ({
|
|
|
18
18
|
loading,
|
|
19
19
|
disabled,
|
|
20
20
|
style,
|
|
21
|
+
icon,
|
|
21
22
|
renderItemLabel,
|
|
22
23
|
renderTriggerLabel,
|
|
23
24
|
onChange
|
|
24
25
|
}) => {
|
|
26
|
+
const selectValueDefault = /* @__PURE__ */ jsx(SelectValue, { placeholder, children: value && renderTriggerLabel ? renderTriggerLabel(value) : void 0 });
|
|
27
|
+
const renderValue = icon ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28
|
+
icon,
|
|
29
|
+
selectValueDefault
|
|
30
|
+
] }) : selectValueDefault;
|
|
25
31
|
return /* @__PURE__ */ jsxs(Select, { value, onValueChange: onChange, children: [
|
|
26
32
|
/* @__PURE__ */ jsx(
|
|
27
33
|
SelectTrigger,
|
|
@@ -32,7 +38,7 @@ const SelectDefault = ({
|
|
|
32
38
|
disabled,
|
|
33
39
|
loading,
|
|
34
40
|
style,
|
|
35
|
-
children:
|
|
41
|
+
children: renderValue
|
|
36
42
|
}
|
|
37
43
|
),
|
|
38
44
|
/* @__PURE__ */ jsx(SelectContent, { children: /* @__PURE__ */ jsx(SelectGroup, { children: options.filter((option) => option.value !== "").map((option) => /* @__PURE__ */ jsx(SelectItem, { className: "cursor-pointer", value: option.value, children: renderItemLabel ? renderItemLabel(option.value) : option.label }, option.value)) }) })
|
package/ui/entries/domain.js
CHANGED
package/ui/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./Input/index.js";
|
|
|
15
15
|
export * from "./InputNumber/index.js";
|
|
16
16
|
export * from "./InputPassword/index.js";
|
|
17
17
|
export * from "./Label/index.js";
|
|
18
|
+
export * from "./MobileNav/index.js";
|
|
18
19
|
export * from "./MultiSelect/index.js";
|
|
19
20
|
export * from "./PageHeader/index.js";
|
|
20
21
|
export * from "./Pagination/index.js";
|