-
Notifications
You must be signed in to change notification settings - Fork 394
refactor: Unify section/group into single Group with collapsible/bordered options #2015
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
69a337a
feat: Extend DashboardContainer schema with tabs and display options
alex-fedotyev 33ea2bb
feat: Add @dnd-kit infrastructure for container reordering
alex-fedotyev cc4d62f
feat: Add GroupContainer, replace SectionHeader
alex-fedotyev 7742b48
feat: Add useDashboardContainers and useTileSelection hooks
alex-fedotyev bcce771
feat: Integrate groups, tabs, and DnD into dashboard page
alex-fedotyev c244076
test: Add GroupContainer and dashboard container tests
alex-fedotyev b018011
Merge branch 'main' into feat/unified-group
alex-fedotyev e501956
fix: use semantic CSS tokens per review feedback
alex-fedotyev 09831a0
fix: Replace remaining Mantine tokens with semantic tokens
alex-fedotyev dbec2c1
fix: Address PR review feedback — Mantine components, decomposition, …
alex-fedotyev 649bea4
fix: Tab delete offers choice — delete tiles or move to another tab
alex-fedotyev 3787fab
Merge remote-tracking branch 'origin/main' into feat/unified-group
alex-fedotyev 08593ae
chore: remove redundant WHAT-style comments per review feedback
alex-fedotyev 7110037
feat: auto-delete emptied groups after Cmd+G regrouping
alex-fedotyev 9c39efa
refactor: rename GroupContainer → DashboardContainer
alex-fedotyev dc153b9
refactor: move activeTabId to URL state (per-viewer)
alex-fedotyev 9c95f42
fix: take viewer to new tab after Add Tab + harden stale activeTabId …
alex-fedotyev b845595
feat: 3-option prompt for group delete (Cancel / Ungroup Tiles / Delete)
alex-fedotyev f059991
fix: address 3 issues from latest code review
alex-fedotyev c01ae19
feat: show group-level alert dot on expanded plain (no-tab) containers
alex-fedotyev 931d80b
fix: address pulpdrew review round (bug fix + design simplifications …
alex-fedotyev 1518b9e
docs: surface dashboard tile selection shortcuts in KeyboardShortcuts…
alex-fedotyev 4cf5bc6
Merge remote-tracking branch 'origin/main' into feat/unified-group
alex-fedotyev 4394704
Merge remote-tracking branch 'origin/main' into feat/unified-group
alex-fedotyev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| "@hyperdx/app": patch | ||
| "@hyperdx/common-utils": patch | ||
| --- | ||
|
|
||
| refactor: Unify section/group into single Group with collapsible/bordered options |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import React from 'react'; | ||
| import { useSortable } from '@dnd-kit/sortable'; | ||
| import { CSS } from '@dnd-kit/utilities'; | ||
| import { Box, Button } from '@mantine/core'; | ||
| import { IconPlus } from '@tabler/icons-react'; | ||
|
|
||
| import { type DragData, type DragHandleProps } from './DashboardDndContext'; | ||
|
|
||
| // --- Empty container placeholder --- | ||
| // Visual placeholder for empty groups/tabs with optional add-tile click. | ||
|
|
||
| export function EmptyContainerPlaceholder({ | ||
| containerId, | ||
| children, | ||
| isEmpty, | ||
| onAddTile, | ||
| }: { | ||
| containerId: string; | ||
| children?: React.ReactNode; | ||
| isEmpty?: boolean; | ||
| onAddTile?: () => void; | ||
| }) { | ||
| return ( | ||
| <div | ||
| data-testid={`container-placeholder-${containerId}`} | ||
| style={{ | ||
| minHeight: isEmpty ? 80 : undefined, | ||
| borderRadius: 4, | ||
| border: isEmpty | ||
| ? '2px dashed var(--mantine-color-default-border)' | ||
| : undefined, | ||
| position: 'relative', | ||
| }} | ||
| > | ||
|
alex-fedotyev marked this conversation as resolved.
Outdated
|
||
| {isEmpty && ( | ||
| <Box | ||
| style={{ | ||
| position: 'absolute', | ||
| inset: 0, | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| padding: '0 16px', | ||
| }} | ||
|
alex-fedotyev marked this conversation as resolved.
Outdated
|
||
| > | ||
| <Button | ||
| variant="secondary" | ||
| fw={400} | ||
| w="100%" | ||
| leftSection={<IconPlus size={16} />} | ||
| onClick={onAddTile} | ||
| > | ||
| Add | ||
| </Button> | ||
| </Box> | ||
| )} | ||
| {children} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| // --- Sortable container wrapper (for container reordering) --- | ||
|
|
||
| export function SortableContainerWrapper({ | ||
| containerId, | ||
| containerTitle, | ||
| children, | ||
| }: { | ||
| containerId: string; | ||
| containerTitle: string; | ||
| children: (dragHandleProps: DragHandleProps) => React.ReactNode; | ||
| }) { | ||
| const { | ||
| attributes, | ||
| listeners, | ||
| setNodeRef, | ||
| transform, | ||
| transition, | ||
| isDragging, | ||
| } = useSortable({ | ||
| id: `container-sort-${containerId}`, | ||
| data: { | ||
| type: 'container', | ||
| containerId, | ||
| containerTitle, | ||
| } satisfies DragData, | ||
| }); | ||
|
|
||
| const style: React.CSSProperties = { | ||
| transform: CSS.Transform.toString(transform), | ||
| transition, | ||
| opacity: isDragging ? 0.5 : 1, | ||
| }; | ||
|
|
||
| return ( | ||
| <div ref={setNodeRef} style={style}> | ||
| {children({ ...attributes, ...listeners })} | ||
| </div> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| import React, { useCallback, useMemo, useState } from 'react'; | ||
| import { | ||
| DndContext, | ||
| DragEndEvent, | ||
| DragOverlay, | ||
| DragStartEvent, | ||
| MouseSensor, | ||
| TouchSensor, | ||
| useSensor, | ||
| useSensors, | ||
| } from '@dnd-kit/core'; | ||
| import { | ||
| SortableContext, | ||
| verticalListSortingStrategy, | ||
| } from '@dnd-kit/sortable'; | ||
| import { DashboardContainer } from '@hyperdx/common-utils/dist/types'; | ||
| import { Box, Text } from '@mantine/core'; | ||
|
|
||
| // --- Types --- | ||
|
|
||
| export type DragHandleProps = React.HTMLAttributes<HTMLElement>; | ||
|
|
||
| export type DragData = { | ||
| type: 'container'; | ||
| containerId: string; | ||
| containerTitle: string; | ||
| }; | ||
|
|
||
| type Props = { | ||
| children: React.ReactNode; | ||
| containers: DashboardContainer[]; | ||
| onReorderContainers: (fromIndex: number, toIndex: number) => void; | ||
| }; | ||
|
|
||
| // --- Provider (container reorder only) --- | ||
|
|
||
| export function DashboardDndProvider({ | ||
| children, | ||
| containers, | ||
| onReorderContainers, | ||
| }: Props) { | ||
| const [activeDrag, setActiveDrag] = useState<DragData | null>(null); | ||
|
|
||
| const mouseSensor = useSensor(MouseSensor, { | ||
| activationConstraint: { distance: 8 }, | ||
| }); | ||
| const touchSensor = useSensor(TouchSensor, { | ||
| activationConstraint: { delay: 200, tolerance: 5 }, | ||
| }); | ||
| const sensors = useSensors(mouseSensor, touchSensor); | ||
|
|
||
| const containerSortableIds = useMemo( | ||
| () => containers.map(c => `container-sort-${c.id}`), | ||
| [containers], | ||
| ); | ||
|
|
||
| const handleDragStart = useCallback((event: DragStartEvent) => { | ||
| setActiveDrag((event.active.data.current as DragData) ?? null); | ||
| }, []); | ||
|
|
||
| const handleDragEnd = useCallback( | ||
| (event: DragEndEvent) => { | ||
| const { active, over } = event; | ||
| setActiveDrag(null); | ||
| if (!over) return; | ||
|
|
||
| const activeData = active.data.current as DragData | undefined; | ||
| if (!activeData) return; | ||
|
|
||
| // Container reorder via sortable | ||
| const overData = over.data.current as DragData | undefined; | ||
| if ( | ||
| overData?.type === 'container' && | ||
| activeData.containerId !== overData.containerId | ||
| ) { | ||
| const from = containers.findIndex(c => c.id === activeData.containerId); | ||
| const to = containers.findIndex(c => c.id === overData.containerId); | ||
| if (from !== -1 && to !== -1) onReorderContainers(from, to); | ||
| } | ||
| }, | ||
| [containers, onReorderContainers], | ||
| ); | ||
|
|
||
| return ( | ||
| <DndContext | ||
| sensors={sensors} | ||
| onDragStart={handleDragStart} | ||
| onDragEnd={handleDragEnd} | ||
| > | ||
| <SortableContext | ||
| items={containerSortableIds} | ||
| strategy={verticalListSortingStrategy} | ||
| > | ||
| {children} | ||
| </SortableContext> | ||
| <DragOverlay dropAnimation={null}> | ||
| {activeDrag && ( | ||
| <Box | ||
| px="sm" | ||
| py={4} | ||
| style={{ | ||
| background: 'var(--mantine-color-body)', | ||
| border: '1px solid var(--mantine-color-default-border)', | ||
|
alex-fedotyev marked this conversation as resolved.
Outdated
|
||
| borderRadius: 4, | ||
| opacity: 0.85, | ||
| }} | ||
| > | ||
| <Text size="sm" fw={500}> | ||
| {activeDrag.containerTitle} | ||
| </Text> | ||
| </Box> | ||
| )} | ||
| </DragOverlay> | ||
| </DndContext> | ||
| ); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.