From 15c2aa858e4f7a078c18852a7c23750c2cec47e7 Mon Sep 17 00:00:00 2001 From: Joao Vilaca Date: Wed, 5 Nov 2025 11:21:50 +0100 Subject: [PATCH 01/13] Remove unused scss file --- web_ui/src/pages/landing-page/landing-page.module.scss | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 web_ui/src/pages/landing-page/landing-page.module.scss diff --git a/web_ui/src/pages/landing-page/landing-page.module.scss b/web_ui/src/pages/landing-page/landing-page.module.scss deleted file mode 100644 index 6bf920d462..0000000000 --- a/web_ui/src/pages/landing-page/landing-page.module.scss +++ /dev/null @@ -1,4 +0,0 @@ -.selectedTab { - width: var(--spectrum-global-dimension-size-1250); - left: 0; -} From 3e05cbe00b9207987261a2704c6799f10ade52c7 Mon Sep 17 00:00:00 2001 From: Joao Vilaca Date: Wed, 5 Nov 2025 11:22:08 +0100 Subject: [PATCH 02/13] Add ManagedTabs --- .../managed-tabs/managed-tabs.component.tsx | 152 ++++++++++++++++++ .../managed-tabs/managed-tabs.module.scss | 84 ++++++++++ 2 files changed, 236 insertions(+) create mode 100644 web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx create mode 100644 web_ui/src/shared/components/managed-tabs/managed-tabs.module.scss diff --git a/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx b/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx new file mode 100644 index 0000000000..754afcf634 --- /dev/null +++ b/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx @@ -0,0 +1,152 @@ +// Copyright (C) 2022-2025 Intel Corporation +// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE + +import { Key, ReactNode } from 'react'; + +import { ActionButton, Flex, Item, Loading, TabList, TabPanels, Tabs, Tooltip, TooltipTrigger } from '@geti/ui'; +import { Add } from '@geti/ui/icons'; + +import { CollapsedItemsPicker } from '../collapsed-items-picker/collapsed-items-picker.component'; +import { TabItem } from '../tabs/tabs.interface'; + +import classes from './managed-tabs.module.scss'; + +export interface AddButtonConfig { + ariaLabel: string; + tooltipText: string; + onPress: () => void; + isLoading?: boolean; + isDisabled?: boolean; + id?: string; +} + +export interface OverflowConfig { + maxVisibleTabs: number; + pickerAriaLabel: string; + onCollapsedItemSelect: (key: string) => void; +} + +export interface ManagedTabsProps { + id?: string; + items: T[]; + selectedKey: string; + onSelectionChange: (key: Key) => void; + renderTabItem: (item: T) => ReactNode; + renderTabPanel: (item: T) => ReactNode; + addButton?: AddButtonConfig; + overflow?: OverflowConfig; + orientation?: 'horizontal' | 'vertical'; + wrapperClassName?: string; + tabListClassName?: string; + ariaLabel: string; +} + +export const ManagedTabs = ({ + id, + items, + selectedKey, + onSelectionChange, + renderTabItem, + renderTabPanel, + addButton, + overflow, + orientation = 'vertical', + wrapperClassName, + tabListClassName, + ariaLabel, +}: ManagedTabsProps) => { + const { visibleItems, collapsedItems, hasSelectedCollapsedItem } = (() => { + if (!overflow || items.length <= overflow.maxVisibleTabs) { + return { + visibleItems: items, + collapsedItems: [], + hasSelectedCollapsedItem: false, + }; + } + + const visible = items.slice(0, overflow.maxVisibleTabs); + const collapsed = items.slice(overflow.maxVisibleTabs); + const hasCollapsedSelection = collapsed.some((item) => item.id === selectedKey); + + return { + visibleItems: visible, + collapsedItems: collapsed, + hasSelectedCollapsedItem: hasCollapsedSelection, + }; + })(); + + const tabItems = visibleItems.map((item) => ({ + id: item.id, + key: item.id, + name: item.name, + children: renderTabPanel(item), + originalItem: item, + })); + + const collapsedPickerItems = collapsedItems.map((item) => ({ id: item.id, name: item.name })); + + return ( + + + +
+ + {(item: TabItem & { originalItem: T }) => ( + + {renderTabItem(item.originalItem)} + + )} + +
+ + {overflow && collapsedItems.length > 0 && ( + + )} + + {addButton && ( + + + {addButton.isLoading ? : } + + {addButton.tooltipText} + + )} +
+ + {(item: TabItem) => {item.children}} +
+
+ ); +}; diff --git a/web_ui/src/shared/components/managed-tabs/managed-tabs.module.scss b/web_ui/src/shared/components/managed-tabs/managed-tabs.module.scss new file mode 100644 index 0000000000..12d431f99f --- /dev/null +++ b/web_ui/src/shared/components/managed-tabs/managed-tabs.module.scss @@ -0,0 +1,84 @@ +/* + Overriding vertical tabs is necessary because if the user's monitor display settings + are higher than 100%, then spectrum's tabs collapses to a Picker. This doesn't happen on + vertical tabs. So we decided to use the vertical tabs logic with horizontal tabs layout. + + https://react-spectrum.adobe.com/react-spectrum/Tabs.html#collapse-overflow-behavior + */ +.componentWrapper { + div[class*='spectrum-Tabs--vertical'] { + border-left: 0 !important; + flex-direction: row !important; + padding-right: var(--spectrum-global-dimension-size-100) !important; + + > div[class*='Tabs-item'] { + margin-bottom: 0 !important; + margin-left: 0 !important; + box-sizing: border-box !important; + display: flex; + align-items: center; + + &:first-child { + padding-left: 0 !important; + } + } + } + + div[class*='TabsPanel--vertical'] { + flex-direction: column !important; + padding: 0 !important; + } +} + +.tabList { + span[class*='spectrum-Tabs-itemLabel'] { + font-size: var(--spectrum-global-dimension-font-size-200); + } + + div[class*='is-selected']::after { + transition: background-color 0.15s ease-in-out; + content: ''; + height: var(--spectrum-global-dimension-size-25); + background-color: transparent; + position: absolute; + left: 0; + right: 0; + bottom: 0; + } + + div[class*='is-selected'] { + position: relative; + + &:after { + background-color: var(--energy-blue); + } + } + + div[class*='spectrum-Tabs-selectionIndicator'] { + display: none; + } + + &.emptySelection { + div[class*='is-selected'] { + color: var(--spectrum-tabs-text-color, var(--spectrum-alias-label-text-color)); + } + } + + > div { + border-bottom-width: 0; + } +} + +.tabWrapper { + border-bottom: var(--spectrum-global-dimension-size-10) solid + var(--spectrum-tabs-rule-color, var(--spectrum-global-color-gray-200)) !important; + box-sizing: border-box !important; + display: flex; + align-items: center; +} + +.tabListScrollContainer { + flex: 1 1 auto; + min-width: 0; + overflow: auto hidden; +} From 144d708ae18b132fcc89e78a5019660501cc2f5e Mon Sep 17 00:00:00 2001 From: Joao Vilaca Date: Wed, 5 Nov 2025 11:22:29 +0100 Subject: [PATCH 03/13] Remove shared styles from custom-tab --- .../custom-tab-item.module.scss | 91 ------------------- 1 file changed, 91 deletions(-) diff --git a/web_ui/src/shared/components/custom-tab-item/custom-tab-item.module.scss b/web_ui/src/shared/components/custom-tab-item/custom-tab-item.module.scss index 2fdee32dfe..c50c02630e 100644 --- a/web_ui/src/shared/components/custom-tab-item/custom-tab-item.module.scss +++ b/web_ui/src/shared/components/custom-tab-item/custom-tab-item.module.scss @@ -18,94 +18,3 @@ border: none !important; } } - -.noneSelected .tabList div[class*='is-selected']::after { - display: none; -} - -/* - Overriding vertical tabs is necessary because if the user's monitor display settings - are higher than 100%, then spectrum's tabs collapses to a Picker. This doesn't happen on - vertical tabs. So we decided to use the vertical tabs logic with horizontal tabs layout. - - https://react-spectrum.adobe.com/react-spectrum/Tabs.html#collapse-overflow-behavior - */ -.componentWrapper { - box-sizing: border-box; - - div[class*='spectrum-Tabs--vertical'] { - border-left: 0 !important; - flex-direction: row !important; - padding-right: var(--spectrum-global-dimension-size-100) !important; - - > div[class*='Tabs-item'] { - margin-bottom: 0 !important; - margin-left: 0 !important; - box-sizing: border-box !important; - display: flex; - align-items: center; - - &:first-child { - padding-left: 0 !important; - } - } - } - - div[class*='TabsPanel--vertical'] { - flex-direction: column !important; - padding: 0 !important; - } -} - -.tabList { - span[class*='spectrum-Tabs-itemLabel'] { - font-size: var(--spectrum-global-dimension-font-size-200); - } - - div[class*='is-selected']::after { - transition: background-color 0.15s ease-in-out; - content: ''; - height: var(--spectrum-global-dimension-size-25); - background-color: transparent; - position: absolute; - left: 0; - right: 0; - bottom: 0; - } - - div[class*='is-selected'] { - position: relative; - - &:after { - background-color: var(--energy-blue); - } - } - - div[class*='spectrum-Tabs-selectionIndicator'] { - display: none; - } - - &.emptySelection { - div[class*='is-selected'] { - color: var(--spectrum-tabs-text-color, var(--spectrum-alias-label-text-color)); - } - } - - > div { - border-bottom-width: 0; - } -} - -.tabWrapper { - border-bottom: var(--spectrum-global-dimension-size-10) solid - var(--spectrum-tabs-rule-color, var(--spectrum-global-color-gray-200)) !important; - box-sizing: border-box !important; - display: flex; - align-items: center; -} - -.tabListScrollContainer { - flex: 1 1 auto; - min-width: 0; - overflow: auto hidden; -} From 066eb67079786700230adaaf324217a5d04c88d0 Mon Sep 17 00:00:00 2001 From: Joao Vilaca Date: Wed, 5 Nov 2025 11:22:57 +0100 Subject: [PATCH 04/13] Use new component on workspaces-tabs --- .../workspaces-tabs.component.tsx | 152 +++++++----------- 1 file changed, 58 insertions(+), 94 deletions(-) diff --git a/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx b/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx index 09f82f0b7a..65d100aef6 100644 --- a/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx +++ b/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx @@ -7,129 +7,93 @@ import { isOrganizationAdmin } from '@geti/core/src/users/user-role-utils'; import { RESOURCE_TYPE } from '@geti/core/src/users/users.interface'; import { useWorkspacesApi } from '@geti/core/src/workspaces/hooks/use-workspaces.hook'; import { WorkspaceEntity } from '@geti/core/src/workspaces/services/workspaces.interface'; -import { ActionButton, Flex, Item, Loading, TabList, TabPanels, Tabs, Tooltip, TooltipTrigger } from '@geti/ui'; -import { Add } from '@geti/ui/icons'; import { useOverlayTriggerState } from 'react-stately'; import { useOrganizationIdentifier } from '../../../hooks/use-organization-identifier/use-organization-identifier.hook'; import { CustomTabItem } from '../../../shared/components/custom-tab-item/custom-tab-item.component'; import { HasPermission } from '../../../shared/components/has-permission/has-permission.component'; import { OPERATION } from '../../../shared/components/has-permission/has-permission.interface'; -import { TabItem } from '../../../shared/components/tabs/tabs.interface'; -import { hasEqualId } from '../../../shared/utils'; +import { ManagedTabs } from '../../../shared/components/managed-tabs/managed-tabs.component'; import { CreateWorkspaceDialog } from '../../user-management/workspaces/create-workspace-dialog/create-workspace-dialog.component'; import { LandingPageWorkspace as Workspace } from '../landing-page-workspace/landing-page-workspace.component'; import { NoPermissionPlaceholder } from './components/no-permission-placeholder.component'; import { CustomTabItemWithMenu } from './custom-tab-item-with-menu.component'; import { useWorkspacesTabs } from './hooks/use-pinned-collapsed-workspace.hook'; -import classes from '../../../shared/components/custom-tab-item/custom-tab-item.module.scss'; +const WorkspaceItem = ({ workspace }: { workspace: WorkspaceEntity }) => { + const { organizationId } = useOrganizationIdentifier(); + const { data: activeUser } = useActiveUser(organizationId); + const { workspaces, selectWorkspace, selectedWorkspaceId } = useWorkspacesTabs(); + const { FEATURE_FLAG_WORKSPACE_ACTIONS } = useFeatureFlags(); + + const isSelected = workspace.id === selectedWorkspaceId; + + if (isSelected && FEATURE_FLAG_WORKSPACE_ACTIONS) { + return ( + } + > + + + ); + } + + return ; +}; export const WorkspacesTabs = () => { const { organizationId } = useOrganizationIdentifier(); - const { workspaces, selectWorkspace, selectedWorkspaceId, handleSelectWorkspace } = useWorkspacesTabs(); + const { workspaces, selectedWorkspaceId, handleSelectWorkspace } = useWorkspacesTabs(); const { FEATURE_FLAG_WORKSPACE_ACTIONS } = useFeatureFlags(); - const { data: activeUser } = useActiveUser(organizationId); const createWorkspaceDialogState = useOverlayTriggerState({}); const { useCreateWorkspaceMutation } = useWorkspacesApi(organizationId); const createWorkspace = useCreateWorkspaceMutation(); - const selectedWorkspace = workspaces.find(hasEqualId(selectedWorkspaceId)); - - const items = workspaces.map(({ id, name }) => ({ - name, - id: `${id === selectedWorkspaceId ? 'selected-' : ''}workspace-${id}`, - key: id, - children: , - })); const workspacesNames = workspaces.map((workspace) => workspace.name); return ( - - + + id='page-layout-id' + items={workspaces} selectedKey={selectedWorkspaceId} - items={items} - aria-label={'Workspaces tabs'} - height={'100%'} - width={'100%'} - orientation={'vertical'} onSelectionChange={handleSelectWorkspace} - > - -
- - {(item: TabItem) => ( - - <> - - {selectedWorkspaceId === item.key && FEATURE_FLAG_WORKSPACE_ACTIONS ? ( - - } - > - - - ) : ( - - )} - - - - )} - -
- - {FEATURE_FLAG_WORKSPACE_ACTIONS && ( - - - - {createWorkspace.isPending ? : } - - Create a new workspace - - - )} -
- - {(item: TabItem) => ( - - } - > - {item.children} - - - )} - -
+ renderTabItem={(workspace) => } + renderTabPanel={() => ( + } + > + + + )} + addButton={ + FEATURE_FLAG_WORKSPACE_ACTIONS + ? { + id: 'create-new-workspace-id', + ariaLabel: 'Create new workspace', + tooltipText: 'Create a new workspace', + onPress: createWorkspaceDialogState.open, + isLoading: createWorkspace.isPending, + } + : undefined + } + ariaLabel='Workspaces tabs' + /> -
+ ); }; From 9c429f565bae392d8a39cd7fa50e8961035d31d4 Mon Sep 17 00:00:00 2001 From: Joao Vilaca Date: Wed, 5 Nov 2025 11:23:16 +0100 Subject: [PATCH 05/13] Use new component on dataset tabs --- .../dataset-tab-list.component.tsx | 76 ------------------- .../project-dataset.component.tsx | 67 +++++++--------- 2 files changed, 29 insertions(+), 114 deletions(-) delete mode 100644 web_ui/src/pages/project-details/components/project-dataset/dataset-tab-list.component.tsx diff --git a/web_ui/src/pages/project-details/components/project-dataset/dataset-tab-list.component.tsx b/web_ui/src/pages/project-details/components/project-dataset/dataset-tab-list.component.tsx deleted file mode 100644 index 818f288da3..0000000000 --- a/web_ui/src/pages/project-details/components/project-dataset/dataset-tab-list.component.tsx +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) 2022-2025 Intel Corporation -// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE - -import { ActionButton, Flex, Item, Loading, TabList, Tooltip, TooltipTrigger } from '@geti/ui'; -import { Add } from '@geti/ui/icons'; - -import { Dataset } from '../../../../core/projects/dataset.interface'; -import { useDataset } from '../../../../providers/dataset-provider/dataset-provider.component'; -import { CollapsedItemsPicker } from '../../../../shared/components/collapsed-items-picker/collapsed-items-picker.component'; -import { TabItem } from '../../../../shared/components/tabs/tabs.interface'; -import { hasEqualId } from '../../../../shared/utils'; -import { useProject } from '../../providers/project-provider/project-provider.component'; -import { ExportDatasetDialog } from './export-dataset/export-dataset-dialog.component'; -import { useExportImportDatasetDialogStates } from './export-dataset/export-import-dataset-dialog-provider.component'; -import { ProjectDatasetTabActions } from './project-dataset-tab-actions.component'; -import { useSelectedDataset } from './use-selected-dataset/use-selected-dataset.hook'; -import { MAX_NUMBER_OF_DISPLAYED_DATASETS } from './utils'; - -import classes from './project-dataset.module.scss'; - -export const DatasetTabList = () => { - const selectedDataset = useSelectedDataset(); - const { project } = useProject(); - - const numberOfDatasets = project.datasets.length; - - const { createDataset, pinnedDatasets, collapsedDatasets, handleCreateDataset, handleSelectDataset } = useDataset(); - - const { exportDialogState } = useExportImportDatasetDialogStates(); - - const hasSelectedPinnedDataset = pinnedDatasets.find(hasEqualId(selectedDataset.id)) !== undefined; - const collapsedPickerItems = collapsedDatasets.map(({ id, name }) => ({ id, name })); - - return ( - - - {(item: TabItem & { dataset: Dataset }) => ( - - - - )} - - - - - {numberOfDatasets > MAX_NUMBER_OF_DISPLAYED_DATASETS ? ( - - ) : null} - - - - {createDataset.isPending ? : } - - Create new testing set - - - ); -}; diff --git a/web_ui/src/pages/project-details/components/project-dataset/project-dataset.component.tsx b/web_ui/src/pages/project-details/components/project-dataset/project-dataset.component.tsx index a2def958d5..a26407b794 100644 --- a/web_ui/src/pages/project-details/components/project-dataset/project-dataset.component.tsx +++ b/web_ui/src/pages/project-details/components/project-dataset/project-dataset.component.tsx @@ -1,19 +1,20 @@ // Copyright (C) 2022-2025 Intel Corporation // LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE -import { Key, useMemo } from 'react'; - -import { Flex, Item, TabPanels, Tabs } from '@geti/ui'; +import { Flex } from '@geti/ui'; import { Dataset } from '../../../../core/projects/dataset.interface'; import { isAnomalyDomain } from '../../../../core/projects/domains'; import { TUTORIAL_CARD_KEYS } from '../../../../core/user-settings/dtos/user-settings.interface'; import { useDataset } from '../../../../providers/dataset-provider/dataset-provider.component'; +import { ManagedTabs } from '../../../../shared/components/managed-tabs/managed-tabs.component'; import { TutorialCardBuilder } from '../../../../shared/components/tutorial-card/tutorial-card-builder.component'; -import { hasEqualId } from '../../../../shared/utils'; import { useProject } from '../../providers/project-provider/project-provider.component'; -import { DatasetTabList } from './dataset-tab-list.component'; import { DatasetTabPanel } from './dataset-tab-panel.component'; +import { ExportDatasetDialog } from './export-dataset/export-dataset-dialog.component'; +import { useExportImportDatasetDialogStates } from './export-dataset/export-import-dataset-dialog-provider.component'; +import { ProjectDatasetTabActions } from './project-dataset-tab-actions.component'; +import { MAX_NUMBER_OF_DISPLAYED_DATASETS } from './utils'; import classes from './project-dataset.module.scss'; @@ -25,22 +26,11 @@ import classes from './project-dataset.module.scss'; datasets to the Picker. The new dataset is placed at the end of pinned datasets. */ export const ProjectDataset = () => { - const { isSingleDomainProject } = useProject(); - const { pinnedDatasets, handleSelectDataset, selectedDataset } = useDataset(); + const { isSingleDomainProject, project } = useProject(); + const { handleSelectDataset, selectedDataset, createDataset, handleCreateDataset } = useDataset(); + const { exportDialogState } = useExportImportDatasetDialogStates(); const isAnomalyProject = isSingleDomainProject(isAnomalyDomain); - const hasSelectedPinnedDataset = pinnedDatasets.find(hasEqualId(selectedDataset.id)) !== undefined; - - const items = useMemo(() => { - return pinnedDatasets.map((dataset) => { - return { - id: `dataset-${dataset.id}-id`, - key: dataset.id, - name: dataset.name, - dataset, - }; - }); - }, [pinnedDatasets]); return ( @@ -50,26 +40,27 @@ export const ProjectDataset = () => { styles={{ fontSize: 'var(--spectrum-global-dimension-font-size-350)' }} /> )} - + items={project.datasets} selectedKey={selectedDataset.id} - onSelectionChange={(key: Key) => handleSelectDataset(String(key))} - > - - - - {(item: { dataset: Dataset }) => ( - - - - )} - - + onSelectionChange={(key) => handleSelectDataset(String(key))} + renderTabItem={(dataset) => } + renderTabPanel={(dataset) => } + addButton={{ + id: 'create-dataset-button-id', + ariaLabel: 'Create dataset', + tooltipText: 'Create new testing set', + onPress: handleCreateDataset, + isLoading: createDataset.isPending, + }} + overflow={{ + maxVisibleTabs: MAX_NUMBER_OF_DISPLAYED_DATASETS, + pickerAriaLabel: 'Collapsed datasets', + onCollapsedItemSelect: handleSelectDataset, + }} + ariaLabel='Dataset page tabs' + /> + ); }; From 75d73af2506fcb8fe366f42656b4563dfbb1407e Mon Sep 17 00:00:00 2001 From: Joao Vilaca Date: Wed, 5 Nov 2025 11:23:28 +0100 Subject: [PATCH 06/13] Use new component on users toolbar --- .../workspace-users-toolbar.component.tsx | 114 +++++++----------- 1 file changed, 46 insertions(+), 68 deletions(-) diff --git a/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx b/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx index 372507abf4..1df29aa0aa 100644 --- a/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx +++ b/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx @@ -1,16 +1,13 @@ // Copyright (C) 2022-2025 Intel Corporation // LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE -import { Key } from 'react'; - import { useFeatureFlags } from '@geti/core/src/feature-flags/hooks/use-feature-flags.hook'; import { useActiveUser } from '@geti/core/src/users/hook/use-users.hook'; import { isOrganizationAdmin } from '@geti/core/src/users/user-role-utils'; import { RESOURCE_TYPE } from '@geti/core/src/users/users.interface'; import { useWorkspacesApi } from '@geti/core/src/workspaces/hooks/use-workspaces.hook'; import { WorkspaceEntity } from '@geti/core/src/workspaces/services/workspaces.interface'; -import { ActionButton, Flex, Item, Loading, TabList, Tabs, Tooltip, TooltipTrigger } from '@geti/ui'; -import { Add } from '@geti/ui/icons'; +import { Flex } from '@geti/ui'; import { useOverlayTriggerState } from 'react-stately'; import { useOrganizationIdentifier } from '../../../hooks/use-organization-identifier/use-organization-identifier.hook'; @@ -18,13 +15,12 @@ import { CustomTabItem } from '../../../shared/components/custom-tab-item/custom import { EditNameDialog } from '../../../shared/components/edit-name-dialog/edit-name-dialog.component'; import { HasPermission } from '../../../shared/components/has-permission/has-permission.component'; import { OPERATION } from '../../../shared/components/has-permission/has-permission.interface'; +import { ManagedTabs } from '../../../shared/components/managed-tabs/managed-tabs.component'; import { WorkspaceDeleteDialog } from '../../landing-page/workspaces-tabs/components/workspace-delete-dialog.component'; import { CustomTabItemWithMenu } from '../../landing-page/workspaces-tabs/custom-tab-item-with-menu.component'; import { useWorkspaceActions } from '../../landing-page/workspaces-tabs/hooks/use-workspace-actions.hook'; import { CreateWorkspaceDialog } from './create-workspace-dialog/create-workspace-dialog.component'; -import classes from '../../../shared/components/custom-tab-item/custom-tab-item.module.scss'; - interface WorkspaceUsersToolbarProps { workspaces: WorkspaceEntity[]; selectedWorkspaceId: string | undefined; @@ -48,71 +44,53 @@ export const WorkspaceUsersToolbar = ({ const { deleteDialog, editDialog } = useWorkspaceActions(workspaces.length, selectedWorkspaceId); - const tabItems = workspaces.map((workspace) => ({ key: workspace.id, name: workspace.name })); const workspacesNames = workspaces.map((workspace) => workspace.name); - const handleSelection = (key: Key) => { - onSelectWorkspace(key.toString()); - }; - return ( - - - -
- - {(item: { key: string; name: string }) => { - return ( - - {item.key === selectedWorkspaceId && FEATURE_FLAG_WORKSPACE_ACTIONS ? ( - } - > - handleSelection(id)} - /> - - ) : ( - - )} - - ); - }} - -
- {FEATURE_FLAG_WORKSPACE_ACTIONS && ( - - - - {createWorkspace.isPending ? : } - - Create workspace - - - )} -
-
+ + + items={workspaces} + selectedKey={selectedWorkspaceId || ''} + onSelectionChange={(key) => onSelectWorkspace(String(key))} + renderTabItem={(workspace) => { + const isSelected = workspace.id === selectedWorkspaceId; + + if (isSelected && FEATURE_FLAG_WORKSPACE_ACTIONS) { + return ( + } + > + + + ); + } + + return ; + }} + renderTabPanel={() => <>} + addButton={ + FEATURE_FLAG_WORKSPACE_ACTIONS + ? { + id: 'create-workspace-toolbar-btn', + ariaLabel: 'Create workspace', + tooltipText: 'Create workspace', + onPress: createWorkspaceDialogState.open, + isLoading: createWorkspace.isPending, + } + : undefined + } + ariaLabel='Workspace tabs' + /> {selectedWorkspace && deleteDialog.deleteWorkspaceDialogState.isOpen && ( Date: Wed, 5 Nov 2025 11:46:43 +0100 Subject: [PATCH 07/13] Copilot --- .../workspaces-tabs.component.tsx | 2 +- .../project-dataset.component.tsx | 16 ++++++++++++---- .../workspace-users-toolbar.component.tsx | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx b/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx index 65d100aef6..26cba3e850 100644 --- a/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx +++ b/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx @@ -87,7 +87,7 @@ export const WorkspacesTabs = () => { } : undefined } - ariaLabel='Workspaces tabs' + ariaLabel={'Workspaces tabs'} /> { - const { isSingleDomainProject, project } = useProject(); - const { handleSelectDataset, selectedDataset, createDataset, handleCreateDataset } = useDataset(); + const { isSingleDomainProject } = useProject(); + const { + handleSelectDataset, + selectedDataset, + createDataset, + handleCreateDataset, + pinnedDatasets, + collapsedDatasets, + } = useDataset(); const { exportDialogState } = useExportImportDatasetDialogStates(); const isAnomalyProject = isSingleDomainProject(isAnomalyDomain); + const allDatasets = [...pinnedDatasets, ...collapsedDatasets]; return ( @@ -41,7 +49,7 @@ export const ProjectDataset = () => { /> )} - items={project.datasets} + items={allDatasets} selectedKey={selectedDataset.id} onSelectionChange={(key) => handleSelectDataset(String(key))} renderTabItem={(dataset) => } @@ -58,7 +66,7 @@ export const ProjectDataset = () => { pickerAriaLabel: 'Collapsed datasets', onCollapsedItemSelect: handleSelectDataset, }} - ariaLabel='Dataset page tabs' + ariaLabel={'Dataset page tabs'} /> diff --git a/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx b/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx index 1df29aa0aa..0483c87339 100644 --- a/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx +++ b/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx @@ -89,7 +89,7 @@ export const WorkspaceUsersToolbar = ({ } : undefined } - ariaLabel='Workspace tabs' + ariaLabel={'Workspace tabs'} /> {selectedWorkspace && deleteDialog.deleteWorkspaceDialogState.isOpen && ( Date: Wed, 5 Nov 2025 13:37:06 +0100 Subject: [PATCH 08/13] Replace iife for just vars --- .../managed-tabs/managed-tabs.component.tsx | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx b/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx index 754afcf634..a7d481707c 100644 --- a/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx +++ b/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx @@ -55,25 +55,11 @@ export const ManagedTabs = ({ tabListClassName, ariaLabel, }: ManagedTabsProps) => { - const { visibleItems, collapsedItems, hasSelectedCollapsedItem } = (() => { - if (!overflow || items.length <= overflow.maxVisibleTabs) { - return { - visibleItems: items, - collapsedItems: [], - hasSelectedCollapsedItem: false, - }; - } + const hasOverflow = overflow && items.length > overflow.maxVisibleTabs; - const visible = items.slice(0, overflow.maxVisibleTabs); - const collapsed = items.slice(overflow.maxVisibleTabs); - const hasCollapsedSelection = collapsed.some((item) => item.id === selectedKey); - - return { - visibleItems: visible, - collapsedItems: collapsed, - hasSelectedCollapsedItem: hasCollapsedSelection, - }; - })(); + const visibleItems = hasOverflow ? items.slice(0, overflow.maxVisibleTabs) : items; + const collapsedItems = hasOverflow ? items.slice(overflow.maxVisibleTabs) : []; + const hasSelectedCollapsedItem = collapsedItems.some((item) => item.id === selectedKey); const tabItems = visibleItems.map((item) => ({ id: item.id, From 51e5bff6f78bf60a88a3455040ba2153a7ce50dc Mon Sep 17 00:00:00 2001 From: Joao Vilaca Date: Wed, 5 Nov 2025 14:43:55 +0100 Subject: [PATCH 09/13] Rename var; Make addButton ReactNode --- .../workspaces-tabs.component.tsx | 29 ++++++++++++------- .../project-dataset.component.tsx | 24 ++++++++++----- .../workspace-users-toolbar.component.tsx | 26 ++++++++++------- .../managed-tabs/managed-tabs.component.tsx | 20 ++----------- 4 files changed, 53 insertions(+), 46 deletions(-) diff --git a/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx b/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx index 26cba3e850..c14aaed03a 100644 --- a/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx +++ b/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx @@ -7,6 +7,8 @@ import { isOrganizationAdmin } from '@geti/core/src/users/user-role-utils'; import { RESOURCE_TYPE } from '@geti/core/src/users/users.interface'; import { useWorkspacesApi } from '@geti/core/src/workspaces/hooks/use-workspaces.hook'; import { WorkspaceEntity } from '@geti/core/src/workspaces/services/workspaces.interface'; +import { ActionButton, Loading, Tooltip, TooltipTrigger } from '@geti/ui'; +import { Add } from '@geti/ui/icons'; import { useOverlayTriggerState } from 'react-stately'; import { useOrganizationIdentifier } from '../../../hooks/use-organization-identifier/use-organization-identifier.hook'; @@ -20,7 +22,7 @@ import { NoPermissionPlaceholder } from './components/no-permission-placeholder. import { CustomTabItemWithMenu } from './custom-tab-item-with-menu.component'; import { useWorkspacesTabs } from './hooks/use-pinned-collapsed-workspace.hook'; -const WorkspaceItem = ({ workspace }: { workspace: WorkspaceEntity }) => { +const WorkspaceTabItem = ({ workspace }: { workspace: WorkspaceEntity }) => { const { organizationId } = useOrganizationIdentifier(); const { data: activeUser } = useActiveUser(organizationId); const { workspaces, selectWorkspace, selectedWorkspaceId } = useWorkspacesTabs(); @@ -66,7 +68,7 @@ export const WorkspacesTabs = () => { items={workspaces} selectedKey={selectedWorkspaceId} onSelectionChange={handleSelectWorkspace} - renderTabItem={(workspace) => } + renderTabItem={(workspace) => } renderTabPanel={() => ( { )} addButton={ - FEATURE_FLAG_WORKSPACE_ACTIONS - ? { - id: 'create-new-workspace-id', - ariaLabel: 'Create new workspace', - tooltipText: 'Create a new workspace', - onPress: createWorkspaceDialogState.open, - isLoading: createWorkspace.isPending, - } - : undefined + FEATURE_FLAG_WORKSPACE_ACTIONS ? ( + + + {createWorkspace.isPending ? : } + + Create a new workspace + + ) : undefined } ariaLabel={'Workspaces tabs'} /> diff --git a/web_ui/src/pages/project-details/components/project-dataset/project-dataset.component.tsx b/web_ui/src/pages/project-details/components/project-dataset/project-dataset.component.tsx index 1057abdb2c..6f957c583d 100644 --- a/web_ui/src/pages/project-details/components/project-dataset/project-dataset.component.tsx +++ b/web_ui/src/pages/project-details/components/project-dataset/project-dataset.component.tsx @@ -1,7 +1,8 @@ // Copyright (C) 2022-2025 Intel Corporation // LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE -import { Flex } from '@geti/ui'; +import { ActionButton, Flex, Loading, Tooltip, TooltipTrigger } from '@geti/ui'; +import { Add } from '@geti/ui/icons'; import { Dataset } from '../../../../core/projects/dataset.interface'; import { isAnomalyDomain } from '../../../../core/projects/domains'; @@ -54,13 +55,20 @@ export const ProjectDataset = () => { onSelectionChange={(key) => handleSelectDataset(String(key))} renderTabItem={(dataset) => } renderTabPanel={(dataset) => } - addButton={{ - id: 'create-dataset-button-id', - ariaLabel: 'Create dataset', - tooltipText: 'Create new testing set', - onPress: handleCreateDataset, - isLoading: createDataset.isPending, - }} + addButton={ + + + {createDataset.isPending ? : } + + Create new testing set + + } overflow={{ maxVisibleTabs: MAX_NUMBER_OF_DISPLAYED_DATASETS, pickerAriaLabel: 'Collapsed datasets', diff --git a/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx b/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx index 0483c87339..dd6fca7388 100644 --- a/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx +++ b/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx @@ -7,7 +7,8 @@ import { isOrganizationAdmin } from '@geti/core/src/users/user-role-utils'; import { RESOURCE_TYPE } from '@geti/core/src/users/users.interface'; import { useWorkspacesApi } from '@geti/core/src/workspaces/hooks/use-workspaces.hook'; import { WorkspaceEntity } from '@geti/core/src/workspaces/services/workspaces.interface'; -import { Flex } from '@geti/ui'; +import { ActionButton, Flex, Loading, Tooltip, TooltipTrigger } from '@geti/ui'; +import { Add } from '@geti/ui/icons'; import { useOverlayTriggerState } from 'react-stately'; import { useOrganizationIdentifier } from '../../../hooks/use-organization-identifier/use-organization-identifier.hook'; @@ -79,15 +80,20 @@ export const WorkspaceUsersToolbar = ({ }} renderTabPanel={() => <>} addButton={ - FEATURE_FLAG_WORKSPACE_ACTIONS - ? { - id: 'create-workspace-toolbar-btn', - ariaLabel: 'Create workspace', - tooltipText: 'Create workspace', - onPress: createWorkspaceDialogState.open, - isLoading: createWorkspace.isPending, - } - : undefined + FEATURE_FLAG_WORKSPACE_ACTIONS ? ( + + + {createWorkspace.isPending ? : } + + Create workspace + + ) : undefined } ariaLabel={'Workspace tabs'} /> diff --git a/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx b/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx index a7d481707c..2ecebc063c 100644 --- a/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx +++ b/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx @@ -3,8 +3,7 @@ import { Key, ReactNode } from 'react'; -import { ActionButton, Flex, Item, Loading, TabList, TabPanels, Tabs, Tooltip, TooltipTrigger } from '@geti/ui'; -import { Add } from '@geti/ui/icons'; +import { Flex, Item, TabList, TabPanels, Tabs } from '@geti/ui'; import { CollapsedItemsPicker } from '../collapsed-items-picker/collapsed-items-picker.component'; import { TabItem } from '../tabs/tabs.interface'; @@ -33,7 +32,7 @@ export interface ManagedTabsProps { onSelectionChange: (key: Key) => void; renderTabItem: (item: T) => ReactNode; renderTabPanel: (item: T) => ReactNode; - addButton?: AddButtonConfig; + addButton?: ReactNode; overflow?: OverflowConfig; orientation?: 'horizontal' | 'vertical'; wrapperClassName?: string; @@ -115,20 +114,7 @@ export const ManagedTabs = ({ /> )} - {addButton && ( - - - {addButton.isLoading ? : } - - {addButton.tooltipText} - - )} + {addButton && addButton} {(item: TabItem) => {item.children}} From b0959d201089b98a3580807a8f9e0bb215386a57 Mon Sep 17 00:00:00 2001 From: Joao Vilaca Date: Wed, 5 Nov 2025 15:38:51 +0100 Subject: [PATCH 10/13] Remove collapsed-items-picker --- .../collapsed-items-picker.component.tsx | 35 ------------------- .../collapsed-items-picker.module.scss | 19 ---------- 2 files changed, 54 deletions(-) delete mode 100644 web_ui/src/shared/components/collapsed-items-picker/collapsed-items-picker.component.tsx delete mode 100644 web_ui/src/shared/components/collapsed-items-picker/collapsed-items-picker.module.scss diff --git a/web_ui/src/shared/components/collapsed-items-picker/collapsed-items-picker.component.tsx b/web_ui/src/shared/components/collapsed-items-picker/collapsed-items-picker.component.tsx deleted file mode 100644 index 6c541bfbbd..0000000000 --- a/web_ui/src/shared/components/collapsed-items-picker/collapsed-items-picker.component.tsx +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2022-2025 Intel Corporation -// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE - -import { Item, Picker } from '@geti/ui'; - -import classes from './collapsed-items-picker.module.scss'; - -interface CollapsedItemsPickerProps { - hasSelectedPinnedItem: boolean; - numberOfCollapsedItems: number; - onSelectionChange: (key: string) => void; - items: { id: string; name: string }[]; - ariaLabel: string; -} - -export const CollapsedItemsPicker = ({ - items, - ariaLabel, - onSelectionChange, - hasSelectedPinnedItem, - numberOfCollapsedItems, -}: CollapsedItemsPickerProps) => { - return ( - onSelectionChange(String(key))} - > - {(item) => {item.name}} - - ); -}; diff --git a/web_ui/src/shared/components/collapsed-items-picker/collapsed-items-picker.module.scss b/web_ui/src/shared/components/collapsed-items-picker/collapsed-items-picker.module.scss deleted file mode 100644 index fa4db6d23f..0000000000 --- a/web_ui/src/shared/components/collapsed-items-picker/collapsed-items-picker.module.scss +++ /dev/null @@ -1,19 +0,0 @@ -.collapsedItemsPicker { - padding-right: var(--spectrum-global-dimension-size-125); - position: relative; - - &.selected::after { - content: ''; - position: absolute; - height: var(--spectrum-global-dimension-size-25); - left: 0; - bottom: -15px; - right: var(--spectrum-global-dimension-size-400); - background-color: white; - } - - span[class*='spectrum-Dropdown-label'] { - font-size: var(--spectrum-global-dimension-font-size-100) !important; - font-style: normal !important; - } -} From 056b5fcc151a2cbfeb4fb860bdfab8b0145c84b2 Mon Sep 17 00:00:00 2001 From: Joao Vilaca Date: Wed, 5 Nov 2025 15:39:02 +0100 Subject: [PATCH 11/13] Update managed tabs --- .../managed-tabs/managed-tabs.component.tsx | 45 +++++++++---------- .../managed-tabs/managed-tabs.module.scss | 23 ++++++++++ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx b/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx index 2ecebc063c..251c56f1bd 100644 --- a/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx +++ b/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx @@ -3,22 +3,12 @@ import { Key, ReactNode } from 'react'; -import { Flex, Item, TabList, TabPanels, Tabs } from '@geti/ui'; +import { Flex, Item, Picker, TabList, TabPanels, Tabs } from '@geti/ui'; -import { CollapsedItemsPicker } from '../collapsed-items-picker/collapsed-items-picker.component'; import { TabItem } from '../tabs/tabs.interface'; import classes from './managed-tabs.module.scss'; -export interface AddButtonConfig { - ariaLabel: string; - tooltipText: string; - onPress: () => void; - isLoading?: boolean; - isDisabled?: boolean; - id?: string; -} - export interface OverflowConfig { maxVisibleTabs: number; pickerAriaLabel: string; @@ -31,7 +21,7 @@ export interface ManagedTabsProps { selectedKey: string; onSelectionChange: (key: Key) => void; renderTabItem: (item: T) => ReactNode; - renderTabPanel: (item: T) => ReactNode; + children?: ReactNode; addButton?: ReactNode; overflow?: OverflowConfig; orientation?: 'horizontal' | 'vertical'; @@ -46,7 +36,7 @@ export const ManagedTabs = ({ selectedKey, onSelectionChange, renderTabItem, - renderTabPanel, + children, addButton, overflow, orientation = 'vertical', @@ -64,7 +54,6 @@ export const ManagedTabs = ({ id: item.id, key: item.id, name: item.name, - children: renderTabPanel(item), originalItem: item, })); @@ -102,22 +91,28 @@ export const ManagedTabs = ({ )} - - {overflow && collapsedItems.length > 0 && ( - - )} + {overflow && collapsedItems.length > 0 && ( + overflow.onCollapsedItemSelect(String(key))} + placeholder={`${collapsedItems.length} more`} + UNSAFE_className={[ + classes.collapsedItemsPicker, + !hasSelectedCollapsedItem ? classes.selected : '', + ].join(' ')} + > + {(item) => {item.name}} + + )} + {addButton && addButton}
- {(item: TabItem) => {item.children}} + {(item: TabItem) => {children}} ); diff --git a/web_ui/src/shared/components/managed-tabs/managed-tabs.module.scss b/web_ui/src/shared/components/managed-tabs/managed-tabs.module.scss index 12d431f99f..e5ba1855a5 100644 --- a/web_ui/src/shared/components/managed-tabs/managed-tabs.module.scss +++ b/web_ui/src/shared/components/managed-tabs/managed-tabs.module.scss @@ -81,4 +81,27 @@ flex: 1 1 auto; min-width: 0; overflow: auto hidden; + display: flex; + align-items: center; + gap: 0; +} + +.collapsedItemsPicker { + position: relative; + flex-shrink: 0; + + &.selected::after { + content: ''; + position: absolute; + height: var(--spectrum-global-dimension-size-25); + left: 0; + bottom: -15px; + right: var(--spectrum-global-dimension-size-400); + background-color: var(--energy-blue); + } + + span[class*='spectrum-Dropdown-label'] { + font-size: var(--spectrum-global-dimension-font-size-100) !important; + font-style: normal !important; + } } From 659a7e09738167fa85f3927e932b22d749791a06 Mon Sep 17 00:00:00 2001 From: Joao Vilaca Date: Wed, 5 Nov 2025 15:39:07 +0100 Subject: [PATCH 12/13] Update consumers --- .../workspaces-tabs.component.tsx | 19 +++++++++---------- .../project-dataset.component.tsx | 5 +++-- .../workspace-users-toolbar.component.tsx | 1 - 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx b/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx index c14aaed03a..98a50c1f9d 100644 --- a/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx +++ b/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx @@ -69,15 +69,6 @@ export const WorkspacesTabs = () => { selectedKey={selectedWorkspaceId} onSelectionChange={handleSelectWorkspace} renderTabItem={(workspace) => } - renderTabPanel={() => ( - } - > - - - )} addButton={ FEATURE_FLAG_WORKSPACE_ACTIONS ? ( @@ -95,7 +86,15 @@ export const WorkspacesTabs = () => { ) : undefined } ariaLabel={'Workspaces tabs'} - /> + > + } + > + + + { selectedKey={selectedDataset.id} onSelectionChange={(key) => handleSelectDataset(String(key))} renderTabItem={(dataset) => } - renderTabPanel={(dataset) => } addButton={ { onCollapsedItemSelect: handleSelectDataset, }} ariaLabel={'Dataset page tabs'} - /> + > + + ); diff --git a/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx b/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx index dd6fca7388..a92230f460 100644 --- a/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx +++ b/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx @@ -78,7 +78,6 @@ export const WorkspaceUsersToolbar = ({ return ; }} - renderTabPanel={() => <>} addButton={ FEATURE_FLAG_WORKSPACE_ACTIONS ? ( From 8607e4e98d00fee05ba74cf7b9932ed7fe95290a Mon Sep 17 00:00:00 2001 From: Joao Vilaca Date: Thu, 6 Nov 2025 09:10:43 +0100 Subject: [PATCH 13/13] Export component --- web_ui/packages/ui/index.ts | 1 + .../src}/managed-tabs/managed-tabs.component.tsx | 15 ++++++++++----- .../ui/src}/managed-tabs/managed-tabs.module.scss | 0 .../workspaces-tabs/workspaces-tabs.component.tsx | 3 +-- .../project-dataset/project-dataset.component.tsx | 3 +-- .../workspace-users-toolbar.component.tsx | 3 +-- 6 files changed, 14 insertions(+), 11 deletions(-) rename web_ui/{src/shared/components => packages/ui/src}/managed-tabs/managed-tabs.component.tsx (93%) rename web_ui/{src/shared/components => packages/ui/src}/managed-tabs/managed-tabs.module.scss (100%) diff --git a/web_ui/packages/ui/index.ts b/web_ui/packages/ui/index.ts index c8e233d2ac..62842613bf 100644 --- a/web_ui/packages/ui/index.ts +++ b/web_ui/packages/ui/index.ts @@ -163,6 +163,7 @@ export { ViewModes, INITIAL_VIEW_MODE, VIEW_MODE_LABEL } from './src/view-modes/ export { useViewMode } from './src/view-modes/use-view-mode.hook'; export { Toast, toast, removeToasts, removeToast, CustomToast } from './src/toast/toast.component'; export { HorizontalLayout, type HorizontalLayoutOptions } from './src/virtualized-horizontal-grid/horizontal-layout'; +export { ManagedTabs } from './src/managed-tabs/managed-tabs.component'; export { ListBox as AriaComponentsListBox, diff --git a/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx b/web_ui/packages/ui/src/managed-tabs/managed-tabs.component.tsx similarity index 93% rename from web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx rename to web_ui/packages/ui/src/managed-tabs/managed-tabs.component.tsx index 251c56f1bd..b19c9a9f38 100644 --- a/web_ui/src/shared/components/managed-tabs/managed-tabs.component.tsx +++ b/web_ui/packages/ui/src/managed-tabs/managed-tabs.component.tsx @@ -3,19 +3,24 @@ import { Key, ReactNode } from 'react'; -import { Flex, Item, Picker, TabList, TabPanels, Tabs } from '@geti/ui'; - -import { TabItem } from '../tabs/tabs.interface'; +import { Flex, Item, Picker, TabList, TabPanels, Tabs } from '@adobe/react-spectrum'; import classes from './managed-tabs.module.scss'; -export interface OverflowConfig { +interface TabItem { + id: string; + key: string; + name: ReactNode; + children: ReactNode; +} + +interface OverflowConfig { maxVisibleTabs: number; pickerAriaLabel: string; onCollapsedItemSelect: (key: string) => void; } -export interface ManagedTabsProps { +interface ManagedTabsProps { id?: string; items: T[]; selectedKey: string; diff --git a/web_ui/src/shared/components/managed-tabs/managed-tabs.module.scss b/web_ui/packages/ui/src/managed-tabs/managed-tabs.module.scss similarity index 100% rename from web_ui/src/shared/components/managed-tabs/managed-tabs.module.scss rename to web_ui/packages/ui/src/managed-tabs/managed-tabs.module.scss diff --git a/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx b/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx index 98a50c1f9d..5aabf5e99b 100644 --- a/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx +++ b/web_ui/src/pages/landing-page/workspaces-tabs/workspaces-tabs.component.tsx @@ -7,7 +7,7 @@ import { isOrganizationAdmin } from '@geti/core/src/users/user-role-utils'; import { RESOURCE_TYPE } from '@geti/core/src/users/users.interface'; import { useWorkspacesApi } from '@geti/core/src/workspaces/hooks/use-workspaces.hook'; import { WorkspaceEntity } from '@geti/core/src/workspaces/services/workspaces.interface'; -import { ActionButton, Loading, Tooltip, TooltipTrigger } from '@geti/ui'; +import { ActionButton, Loading, ManagedTabs, Tooltip, TooltipTrigger } from '@geti/ui'; import { Add } from '@geti/ui/icons'; import { useOverlayTriggerState } from 'react-stately'; @@ -15,7 +15,6 @@ import { useOrganizationIdentifier } from '../../../hooks/use-organization-ident import { CustomTabItem } from '../../../shared/components/custom-tab-item/custom-tab-item.component'; import { HasPermission } from '../../../shared/components/has-permission/has-permission.component'; import { OPERATION } from '../../../shared/components/has-permission/has-permission.interface'; -import { ManagedTabs } from '../../../shared/components/managed-tabs/managed-tabs.component'; import { CreateWorkspaceDialog } from '../../user-management/workspaces/create-workspace-dialog/create-workspace-dialog.component'; import { LandingPageWorkspace as Workspace } from '../landing-page-workspace/landing-page-workspace.component'; import { NoPermissionPlaceholder } from './components/no-permission-placeholder.component'; diff --git a/web_ui/src/pages/project-details/components/project-dataset/project-dataset.component.tsx b/web_ui/src/pages/project-details/components/project-dataset/project-dataset.component.tsx index 0d5c5b331e..734dd358ae 100644 --- a/web_ui/src/pages/project-details/components/project-dataset/project-dataset.component.tsx +++ b/web_ui/src/pages/project-details/components/project-dataset/project-dataset.component.tsx @@ -1,14 +1,13 @@ // Copyright (C) 2022-2025 Intel Corporation // LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE -import { ActionButton, Flex, Loading, Tooltip, TooltipTrigger } from '@geti/ui'; +import { ActionButton, Flex, Loading, ManagedTabs, Tooltip, TooltipTrigger } from '@geti/ui'; import { Add } from '@geti/ui/icons'; import { Dataset } from '../../../../core/projects/dataset.interface'; import { isAnomalyDomain } from '../../../../core/projects/domains'; import { TUTORIAL_CARD_KEYS } from '../../../../core/user-settings/dtos/user-settings.interface'; import { useDataset } from '../../../../providers/dataset-provider/dataset-provider.component'; -import { ManagedTabs } from '../../../../shared/components/managed-tabs/managed-tabs.component'; import { TutorialCardBuilder } from '../../../../shared/components/tutorial-card/tutorial-card-builder.component'; import { useProject } from '../../providers/project-provider/project-provider.component'; import { DatasetTabPanel } from './dataset-tab-panel.component'; diff --git a/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx b/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx index a92230f460..66c8e33408 100644 --- a/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx +++ b/web_ui/src/pages/user-management/workspaces/workspace-users-toolbar.component.tsx @@ -7,7 +7,7 @@ import { isOrganizationAdmin } from '@geti/core/src/users/user-role-utils'; import { RESOURCE_TYPE } from '@geti/core/src/users/users.interface'; import { useWorkspacesApi } from '@geti/core/src/workspaces/hooks/use-workspaces.hook'; import { WorkspaceEntity } from '@geti/core/src/workspaces/services/workspaces.interface'; -import { ActionButton, Flex, Loading, Tooltip, TooltipTrigger } from '@geti/ui'; +import { ActionButton, Flex, Loading, ManagedTabs, Tooltip, TooltipTrigger } from '@geti/ui'; import { Add } from '@geti/ui/icons'; import { useOverlayTriggerState } from 'react-stately'; @@ -16,7 +16,6 @@ import { CustomTabItem } from '../../../shared/components/custom-tab-item/custom import { EditNameDialog } from '../../../shared/components/edit-name-dialog/edit-name-dialog.component'; import { HasPermission } from '../../../shared/components/has-permission/has-permission.component'; import { OPERATION } from '../../../shared/components/has-permission/has-permission.interface'; -import { ManagedTabs } from '../../../shared/components/managed-tabs/managed-tabs.component'; import { WorkspaceDeleteDialog } from '../../landing-page/workspaces-tabs/components/workspace-delete-dialog.component'; import { CustomTabItemWithMenu } from '../../landing-page/workspaces-tabs/custom-tab-item-with-menu.component'; import { useWorkspaceActions } from '../../landing-page/workspaces-tabs/hooks/use-workspace-actions.hook';