diff --git a/apps/design-system/src/App.tsx b/apps/design-system/src/App.tsx index 57639a549f..55fc649ca0 100644 --- a/apps/design-system/src/App.tsx +++ b/apps/design-system/src/App.tsx @@ -4,8 +4,8 @@ import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom' import ViewPreview from '@/pages/view-preview/view-preview' import { useThemeStore } from '@utils/theme-utils' -import { DialogProvider, TooltipProvider } from '@harnessio/ui/components' -import { ThemeProvider, TranslationProvider } from '@harnessio/ui/context' +import { TooltipProvider } from '@harnessio/ui/components' +import { DialogProvider, ThemeProvider, TranslationProvider } from '@harnessio/ui/context' import AppRouterProvider from './AppRouterProvider' diff --git a/apps/design-system/src/AppRouterProvider.tsx b/apps/design-system/src/AppRouterProvider.tsx index 45fc376285..4d241bd1f0 100644 --- a/apps/design-system/src/AppRouterProvider.tsx +++ b/apps/design-system/src/AppRouterProvider.tsx @@ -10,24 +10,23 @@ import { useSearchParams } from 'react-router-dom' -import { Button, RbacButtonProps, SplitButton, toButtonProps, Tooltip } from '@harnessio/ui/components' +import { + Button, + MoreActionsTooltip, + RbacButtonProps, + rbacTooltip, + SplitButton, + Tooltip +} from '@harnessio/ui/components' import { ComponentProvider, RouterContextProvider } from '@harnessio/ui/context' const RbacButton = ({ rbac: _, tooltip, ...rest }: RbacButtonProps) => { const hasPermission = true - const button = ( - + console.log('Action 1') }, + { title: 'Action 2', onClick: () => console.log('Action 2') } + ]} + buttonVariant="outline" + disabled={isLoading} + /> + + ) + return ( { logoName="node-js" title="harnessdocker" description="Preconfigured Docker connector for use in Harness." - button={{ - props: { - onClick: () => console.log('Click') - }, - text: 'Test Connection' - }} - moreActions={[ - { title: 'Action 1', onClick: () => console.log('Action 1') }, - { title: 'Action 2', onClick: () => console.log('Action 2') } - ]} + actions={actions} > Labels: @@ -169,17 +193,18 @@ export const ViewOnlyView = () => { }, - { label: 'Last updated', value: null }, + { label: 'Last updated', value: undefined }, { label: 'Last status check', value: }, - { label: 'Last successful check', value: null }, + { label: 'Last successful check', value: undefined }, { label: 'Connection status', value: ( @@ -200,10 +225,10 @@ export const ViewOnlyView = () => { - + {dataMock.map((props, index) => ( - + ))} @@ -214,7 +239,7 @@ export const ViewOnlyView = () => { }} > {dataMock.slice(0, 2).map((props, index) => ( - + ))} diff --git a/apps/design-system/src/utils/fileViewUtils.tsx b/apps/design-system/src/utils/fileViewUtils.tsx index e6a9f2c827..8d19899863 100644 --- a/apps/design-system/src/utils/fileViewUtils.tsx +++ b/apps/design-system/src/utils/fileViewUtils.tsx @@ -22,7 +22,13 @@ export const renderEntries = (entries: OpenapiContentInfo[], parentPath: string if (item.type === 'file') { return ( - + {item.name} ) @@ -30,7 +36,7 @@ export const renderEntries = (entries: OpenapiContentInfo[], parentPath: string return ( - - - - - - - - - - - + + + + + + + + + diff --git a/apps/gitness/src/AppMFE.tsx b/apps/gitness/src/AppMFE.tsx index 3fc4179542..00be4581a4 100644 --- a/apps/gitness/src/AppMFE.tsx +++ b/apps/gitness/src/AppMFE.tsx @@ -5,8 +5,8 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom' import { QueryClientProvider } from '@tanstack/react-query' import { CodeServiceAPIClient } from '@harnessio/code-service-client' -import { DialogProvider, Toast, TooltipProvider } from '@harnessio/ui/components' -import { PortalProvider, TranslationProvider } from '@harnessio/ui/context' +import { TooltipProvider } from '@harnessio/ui/components' +import { DialogProvider, PortalProvider, TranslationProvider } from '@harnessio/ui/context' import { ExitConfirmProvider } from './framework/context/ExitConfirmContext' import { MFEContext, MFEContextProps } from './framework/context/MFEContext' @@ -107,17 +107,15 @@ export default function AppMFE({ - - - - - - - - - - - + + + + + + + + + diff --git a/apps/gitness/src/components-v2/FileExplorer.tsx b/apps/gitness/src/components-v2/FileExplorer.tsx index 7912acb814..35be283240 100644 --- a/apps/gitness/src/components-v2/FileExplorer.tsx +++ b/apps/gitness/src/components-v2/FileExplorer.tsx @@ -96,6 +96,7 @@ export default function Explorer({ selectedBranch, repoDetails, isLoading: isLoa if (item.type === 'file') { return ( ( ( @@ -37,7 +38,8 @@ export const BranchSelectorContainer = forwardRef { @@ -133,6 +135,7 @@ export const BranchSelectorContainer = forwardRef ) } diff --git a/apps/gitness/src/data/navbar-menu-data.ts b/apps/gitness/src/data/navbar-menu-data.ts index 54c1878c47..abe75a1259 100644 --- a/apps/gitness/src/data/navbar-menu-data.ts +++ b/apps/gitness/src/data/navbar-menu-data.ts @@ -209,7 +209,7 @@ export const getNavbarMenuData: GetNavbarMenuData = ({ t, spaceId, repoId, route }, { id: 23, - iconName: 'page', + iconName: 'empty-page', title: t('component:navbar.file-store'), to: routes.toFileStore() }, @@ -326,7 +326,7 @@ export const getNavbarMenuData: GetNavbarMenuData = ({ t, spaceId, repoId, route items: [ { id: 39, - iconName: 'externaltickets', + iconName: 'external-tickets', title: t('component:navbar.external-tickets'), to: routes.toExternalTickets() } diff --git a/apps/gitness/src/framework/context/AppContext.tsx b/apps/gitness/src/framework/context/AppContext.tsx index c8185f21cc..4af429cf41 100644 --- a/apps/gitness/src/framework/context/AppContext.tsx +++ b/apps/gitness/src/framework/context/AppContext.tsx @@ -11,11 +11,10 @@ import { updateUser, UpdateUserErrorResponse } from '@harnessio/code-service-client' +import { useLocalStorage, UserPreference } from '@harnessio/ui/hooks' import { ProfileSettingsErrorType } from '@harnessio/ui/views' import { useIsMFE } from '../hooks/useIsMFE' -import useLocalStorage from '../hooks/useLocalStorage' -import { useMFEContext } from '../hooks/useMFEContext' import usePageTitle from '../hooks/usePageTitle' interface AppContextType { @@ -52,11 +51,7 @@ const AppContext = createContext({ export const AppProvider: FC<{ children: ReactNode }> = memo(({ children }) => { usePageTitle() const isMFE = useIsMFE() - const { - parentContextObj: { appStoreContext } - } = useMFEContext() - const parentAppStoreContext = useContext(appStoreContext) - const [currentUser, setCurrentUser] = useLocalStorage('currentUser', {}) + const [currentUser, setCurrentUser] = useLocalStorage(UserPreference.CURRENT_USER, {}) const [spaces, setSpaces] = useState([]) const [isSpacesLoading, setSpacesIsLoading] = useState(false) const [isLoadingUser, setIsLoadingUser] = useState(false) @@ -68,12 +63,10 @@ export const AppProvider: FC<{ children: ReactNode }> = memo(({ children }) => { const fetchUser = async (): Promise => { try { - if (!isMFE) { - setIsLoadingUser(true) - setUpdateUserError(null) - const userResponse = await getUser({}) - setCurrentUser(userResponse.body) - } + setIsLoadingUser(true) + setUpdateUserError(null) + const userResponse = await getUser({}) + setCurrentUser(userResponse.body) } catch (error) { const typedError = error as GetUserErrorResponse setUpdateUserError({ @@ -102,7 +95,7 @@ export const AppProvider: FC<{ children: ReactNode }> = memo(({ children }) => { } } - const fetchSpacesAndUser = async () => { + const fetchSpaces = async () => { setSpacesIsLoading(true) try { @@ -111,11 +104,9 @@ export const AppProvider: FC<{ children: ReactNode }> = memo(({ children }) => { queryParams: { page: 1, limit: 100, sort: 'identifier', order: 'asc' } }) - const promises = isMFE ? [] : [fetchSpaces(), fetchUser()] - const [results] = await Promise.allSettled(promises) - - if (!isMFE && results.status === 'fulfilled' && results.value?.body) { - const spaces = results.value.body + const results = await fetchSpaces() + if (results?.body) { + const spaces = results.body .filter((item: { space?: TypesSpace }) => item.space) .map((item: { space?: TypesSpace }) => item.space as TypesSpace) @@ -129,17 +120,9 @@ export const AppProvider: FC<{ children: ReactNode }> = memo(({ children }) => { } useEffect(() => { - if (isMFE) { - const currentUserInfo = parentAppStoreContext.currentUserInfo - setCurrentUser({ - uid: currentUserInfo.uuid, - email: currentUserInfo.email, - display_name: currentUserInfo.name, - created: currentUserInfo.createdAt, - updated: currentUserInfo.lastUpdatedAt - }) - } else { - fetchSpacesAndUser() + fetchUser() + if (!isMFE) { + fetchSpaces() } }, [isMFE]) diff --git a/apps/gitness/src/framework/context/MFEContext.tsx b/apps/gitness/src/framework/context/MFEContext.tsx index d0fe9f3f45..61d92109c6 100644 --- a/apps/gitness/src/framework/context/MFEContext.tsx +++ b/apps/gitness/src/framework/context/MFEContext.tsx @@ -56,9 +56,15 @@ export declare const useLogout: () => UseLogoutReturn export declare const usePermission: (permissionsRequest?: PermissionsRequest, deps?: Array) => Array +export enum FeatureFlag { + SEMANTIC_SEARCH_ENABLED = 'SEMANTIC_SEARCH_ENABLED', + CODE_SSH_ENABLED = 'CODE_SSH_ENABLED', + CODE_SECURITY_SCANNING_ON_PUSH = 'CODE_SECURITY_SCANNING_ON_PUSH' +} + export interface Hooks { - useLogout?: typeof useLogout usePermission?: typeof usePermission + useLogout?: typeof useLogout } /**************/ @@ -77,6 +83,7 @@ export interface MFEContextProps { customHooks: Partial<{ useGenerateToken: Unknown usePreferenceStore: Unknown + useFeatureFlags: Unknown }> customUtils: Partial<{ navigateToUserProfile: Unknown @@ -125,7 +132,8 @@ export const defaultContext: MFEContextProps = { appStoreContext: createContext({ currentUserInfo: { uuid: '' } }) }, customHooks: { - usePreferenceStore: () => ({ preference: undefined, setPreference: noop }) + usePreferenceStore: () => ({ preference: undefined, setPreference: noop }), + useFeatureFlags: () => ({}) }, customUtils: {}, routes: {}, diff --git a/apps/gitness/src/framework/hooks/useIsMFE.ts b/apps/gitness/src/framework/hooks/useIsMFE.ts index 9a7f8deb0c..b8671c2196 100644 --- a/apps/gitness/src/framework/hooks/useIsMFE.ts +++ b/apps/gitness/src/framework/hooks/useIsMFE.ts @@ -1,6 +1,8 @@ +import { isEmpty } from 'lodash-es' + import { useMFEContext } from './useMFEContext' export const useIsMFE = () => { const mfeContext = useMFEContext() - return mfeContext.renderUrl !== '' + return !isEmpty(mfeContext.renderUrl) } diff --git a/apps/gitness/src/framework/hooks/useLocalStorage.ts b/apps/gitness/src/framework/hooks/useLocalStorage.ts deleted file mode 100644 index b97bbfe202..0000000000 --- a/apps/gitness/src/framework/hooks/useLocalStorage.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useState } from 'react' - -function useLocalStorage(key: string, initialValue: T) { - // Initialize state from localStorage or fallback to initialValue - const [storedValue, setStoredValue] = useState(() => { - const item = localStorage.getItem(key) - return item ? JSON.parse(item) : initialValue - }) - - // Update both state and localStorage - const setValue = (value: T) => { - setStoredValue(value) - localStorage.setItem(key, JSON.stringify(value)) - } - - return [storedValue, setValue] as const -} - -export default useLocalStorage diff --git a/apps/gitness/src/framework/hooks/useRepoImportWithPubSub.tsx b/apps/gitness/src/framework/hooks/useRepoImportWithPubSub.tsx index 0fe786448f..5ab9f7d302 100644 --- a/apps/gitness/src/framework/hooks/useRepoImportWithPubSub.tsx +++ b/apps/gitness/src/framework/hooks/useRepoImportWithPubSub.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect } from 'react' import { useParams } from 'react-router-dom' import { RepoRepositoryOutput } from '@harnessio/code-service-client' -import { Link, useToast } from '@harnessio/ui/components' +import { Link, toast } from '@harnessio/ui/components' import { useRepoStore } from '../../pages-v2/repo/stores/repo-list-store' import { transformRepoList } from '../../pages-v2/repo/transform-utils/repo-list-transform' @@ -12,7 +12,6 @@ import { useRoutes } from '../context/NavigationContext' import { eventManager } from '../event/EventManager' export const useRepoImportWithPubSub = () => { - const { update, toast } = useToast() const routes = useRoutes() const { spaceId } = useParams() @@ -23,22 +22,19 @@ export const useRepoImportWithPubSub = () => { if (importToastId && importRepoIdentifier && importRepoIdentifier === eventData.identifier) { try { - update({ - id: importToastId, - open: false - }) + toast.dismiss(importToastId) } catch (error) { console.error('No toast to dismiss:', error) } - toast({ + toast.success({ title: 'Successfully imported', description: ( {importRepoIdentifier} ), - action: null, - duration: 5000, - variant: 'success' + options: { + duration: 5000 + } }) const transformedRepo = transformRepoList([eventData]) @@ -48,21 +44,25 @@ export const useRepoImportWithPubSub = () => { setImportToastId(null) } }, - [routes, spaceId, toast, update] + [routes, spaceId] ) const handleError = useCallback(() => { const { importToastId } = useRepoStore.getState() if (importToastId) { - update({ - id: importToastId, + try { + toast.dismiss(importToastId) + } catch (error) { + console.error('No toast to dismiss:', error) + } + + toast.danger({ title: 'Import failed', - variant: 'failed', - action: null + description: 'Failed to import repository' }) } - }, [update]) + }, []) useEffect(() => { eventManager.subscribe(SSEEvent.REPO_IMPORTED, handleImportEvent) diff --git a/apps/gitness/src/framework/hooks/useSelectedSpaceId.ts b/apps/gitness/src/framework/hooks/useSelectedSpaceId.ts index 6753c60c40..2e77fad50c 100644 --- a/apps/gitness/src/framework/hooks/useSelectedSpaceId.ts +++ b/apps/gitness/src/framework/hooks/useSelectedSpaceId.ts @@ -1,11 +1,12 @@ import { useEffect } from 'react' +import { useLocalStorage, UserPreference } from '@harnessio/ui/hooks' + import { useAppContext } from '../context/AppContext' -import useLocalStorage from './useLocalStorage' export function useSelectedSpaceId(value?: string) { const { spaces } = useAppContext() - const [spaceId, setSpaceId] = useLocalStorage('spaceId', value) + const [spaceId, setSpaceId] = useLocalStorage(UserPreference.SPACE_ID, value) useEffect(() => { if (value) { diff --git a/apps/gitness/src/framework/rbac/rbac-button.tsx b/apps/gitness/src/framework/rbac/rbac-button.tsx index 28ad89cb03..ec1dbca404 100644 --- a/apps/gitness/src/framework/rbac/rbac-button.tsx +++ b/apps/gitness/src/framework/rbac/rbac-button.tsx @@ -1,4 +1,4 @@ -import { Button, RbacButtonProps, Resource, toButtonProps, Tooltip } from '@harnessio/ui/components' +import { Button, RbacButtonProps, rbacTooltip, Resource, Tooltip } from '@harnessio/ui/components' import { useMFEContext } from '../hooks/useMFEContext' @@ -16,18 +16,10 @@ export const RbacButton = ({ rbac, tooltip, ...rest }: RbacButtonProps) => { }) ?.some(Boolean) ?? true - const button = ( - + + + + + + +
+ +

Combined with Manual Loading State

+ +
+ + + +
+`} +/> + ## Additional Features When the `iconOnly` prop is used, you must also provide `tooltipProps` to display content in the tooltip. diff --git a/apps/portal/src/content/docs/components/calendar.mdx b/apps/portal/src/content/docs/components/calendar.mdx index 14845c6908..bcfb9e2eb9 100644 --- a/apps/portal/src/content/docs/components/calendar.mdx +++ b/apps/portal/src/content/docs/components/calendar.mdx @@ -12,6 +12,7 @@ import { DocsPage } from "@/components/docs-page"; client:only code={`() => { const [selectedDate, setSelectedDate] = React.useState([new Date()]); + const [month, setMonth] = React.useState(new Date()); return ( ); diff --git a/apps/portal/src/content/docs/components/card-select.mdx b/apps/portal/src/content/docs/components/card-select.mdx index dac21e7cfe..be734027ba 100644 --- a/apps/portal/src/content/docs/components/card-select.mdx +++ b/apps/portal/src/content/docs/components/card-select.mdx @@ -63,7 +63,7 @@ The `CardSelect` supports two selection types: client:only code={`
-

Single Selection

+ Single Selection Option 1 @@ -77,7 +77,7 @@ The `CardSelect` supports two selection types:
-

Multiple Selection

+ Multiple Selection Option A @@ -100,7 +100,7 @@ Cards can be arranged horizontally, vertically, or in a grid: client:only code={`
-

Vertical Layout (Default)

+ Vertical Layout (Default) First Card @@ -114,9 +114,9 @@ Cards can be arranged horizontally, vertically, or in a grid:
-

Horizontal Layout

+ Horizontal Layout - + First Card Cards arranged side by side @@ -128,7 +128,7 @@ Cards can be arranged horizontally, vertically, or in a grid:
-

Grid Layout

+ Grid Layout Grid Item 1 @@ -156,7 +156,7 @@ Control the gap between cards using the `gap` prop: client:only code={`
-

XS Gap

+ XS Gap XS 1 @@ -171,7 +171,7 @@ Control the gap between cards using the `gap` prop:
-

Small Gap

+ Small Gap Small 1 @@ -186,7 +186,7 @@ Control the gap between cards using the `gap` prop:
-

Medium Gap (Default)

+ Medium Gap (Default) Medium 1 @@ -201,7 +201,7 @@ Control the gap between cards using the `gap` prop:
-

Large Gap

+ Large Gap Large 1 @@ -244,7 +244,7 @@ Cards can be disabled either individually or as a group: client:only code={`
-

Individual Cards Disabled

+ Individual Cards Disabled Enabled Card @@ -256,7 +256,7 @@ Cards can be disabled either individually or as a group:
-

Entire Group Disabled

+ Entire Group Disabled First Option diff --git a/apps/portal/src/content/docs/components/carousel.mdx b/apps/portal/src/content/docs/components/carousel.mdx index d0b5fe4acf..f678c72bba 100644 --- a/apps/portal/src/content/docs/components/carousel.mdx +++ b/apps/portal/src/content/docs/components/carousel.mdx @@ -15,13 +15,13 @@ import { DocsPage } from "@/components/docs-page"; -
Slide 1
+
Slide 1
-
Slide 2
+
Slide 2
-
Slide 3
+
Slide 3
@@ -39,13 +39,13 @@ return ( -
Slide 1
+
Slide 1
-
Slide 2
+
Slide 2
-
Slide 3
+
Slide 3
@@ -129,13 +129,13 @@ The `Content` component defines the main content area of the carousel. ```typescript jsx -
Slide 1
+
Slide 1
-
Slide 2
+
Slide 2
-
Slide 3
+
Slide 3
``` @@ -169,7 +169,7 @@ The `Item` component defines an individual item within the carousel. ```typescript jsx -
Slide 1
+
Slide 1
``` diff --git a/apps/portal/src/content/docs/components/checkbox.mdx b/apps/portal/src/content/docs/components/checkbox.mdx index 8a32f77a2e..2f1c7ee253 100644 --- a/apps/portal/src/content/docs/components/checkbox.mdx +++ b/apps/portal/src/content/docs/components/checkbox.mdx @@ -23,7 +23,7 @@ import { DocsPage } from "@/components/docs-page";
{/* Standard usage */}
-

Standard usage

+ Standard usage -

Indeterminate state

+ Indeterminate state -

Optional

+ Optional -

Disabled

+ Disabled -

Without Label

+ Without Label -

Error state

+ Error state
- Outline: + Outline:
12 42 @@ -90,7 +90,7 @@ Compare both variants side by side to see the visual differences:
- Secondary: + Secondary:
12 42 diff --git a/apps/portal/src/content/docs/components/data-table.mdx b/apps/portal/src/content/docs/components/data-table.mdx index 70be712ede..6a9be50fda 100644 --- a/apps/portal/src/content/docs/components/data-table.mdx +++ b/apps/portal/src/content/docs/components/data-table.mdx @@ -78,6 +78,14 @@ return ( onSortingChange={setTableSorting} getRowCanExpand={(row) => row.original.status === "active"} getRowCanSelect={(row) => row.original.status === "active"} + paginationProps={{ + pageSize: 10, + totalItems: 50, + currentPage: 1, + goToPage: (page) => console.log(page), + pageSizeOptions: [5, 10, 20], + onPageSizeChange: (size) => console.log(size), + }} /> ) }`} /> @@ -200,7 +208,7 @@ function MyTable() { value: "(row: TData) => string", }, { - name: "pagination", + name: "paginationProps", description: "Pagination configuration", required: false, value: "PaginationProps", diff --git a/apps/portal/src/content/docs/components/dialog.mdx b/apps/portal/src/content/docs/components/dialog.mdx index aa85818926..8df904fdb6 100644 --- a/apps/portal/src/content/docs/components/dialog.mdx +++ b/apps/portal/src/content/docs/components/dialog.mdx @@ -101,7 +101,7 @@ The `Dialog` component offers four size options: `sm`, `md`, `lg`, and `max` (wh client:only code={`
-

Small (Default)

+ Small (Default) @@ -119,7 +119,9 @@ The `Dialog` component offers four size options: `sm`, `md`, `lg`, and `max` (wh
-

Medium

+ + Medium + @@ -137,7 +139,9 @@ The `Dialog` component offers four size options: `sm`, `md`, `lg`, and `max` (wh
-

Large

+ + Large + @@ -155,7 +159,7 @@ The `Dialog` component offers four size options: `sm`, `md`, `lg`, and `max` (wh
-

Maximized

+ Maximized @@ -198,7 +202,7 @@ Headers can have themes `default`, `warning` and `danger` with matching icons: client:only code={`
-

Default Theme

+ Default Theme @@ -216,7 +220,7 @@ Headers can have themes `default`, `warning` and `danger` with matching icons:
-

Warning Theme

+ Warning Theme @@ -236,7 +240,7 @@ Headers can have themes `default`, `warning` and `danger` with matching icons:
-

Danger Theme

+ Danger Theme @@ -347,7 +351,7 @@ The Body component automatically handles scrolling for long content:
{Array.from({ length: 20 }).map((_, i) => (
-

Section {i + 1}

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit

))} @@ -372,7 +376,7 @@ The Body component automatically handles scrolling for long content:
{Array.from({ length: 20 }).map((_, i) => (
-

Section {i + 1}

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit

))} diff --git a/apps/portal/src/content/docs/components/drawer.mdx b/apps/portal/src/content/docs/components/drawer.mdx index 73f899047b..6ebea2abcb 100644 --- a/apps/portal/src/content/docs/components/drawer.mdx +++ b/apps/portal/src/content/docs/components/drawer.mdx @@ -14,7 +14,7 @@ import { DocsPage } from "@/components/docs-page"; client:only code={` - + @@ -105,7 +105,7 @@ return ( open={isDrawerOpen} onOpenChange={setIsDrawerOpen} > - + @@ -120,7 +120,7 @@ return ( Content of the drawer goes here - + @@ -200,7 +200,7 @@ Nested drawers can be implemented by placing a `Drawer.Root` inside the parent ` - + @@ -211,7 +211,7 @@ Nested drawers can be implemented by placing a `Drawer.Root` inside the parent ` - + @@ -286,14 +286,38 @@ Nested drawers can be implemented by placing a `Drawer.Root` inside the parent ` ## Drawer sizes -The `Drawer.Content` component accepts a `size` prop to control its width (or height, depending on `direction`). Supported values are: `xs`, `sm`, `md`, and `lg`. +The `Drawer.Content` component accepts a `size` prop to control its width (or height, depending on `direction`). Supported values are: `2xs`, `xs`, `sm`, `md`, and `lg`. - + + + + + + + Title + + + + + + + + + + + + + + + + + + @@ -317,7 +341,7 @@ The `Drawer.Content` component accepts a `size` prop to control its width (or he - + @@ -341,7 +365,7 @@ The `Drawer.Content` component accepts a `size` prop to control its width (or he - + @@ -365,7 +389,7 @@ The `Drawer.Content` component accepts a `size` prop to control its width (or he - + @@ -401,7 +425,7 @@ The `Drawer.Root` component accepts a `direction` prop to control which side the code={` - + @@ -425,7 +449,7 @@ The `Drawer.Root` component accepts a `direction` prop to control which side the - + @@ -449,7 +473,7 @@ The `Drawer.Root` component accepts a `direction` prop to control which side the - + @@ -473,7 +497,7 @@ The `Drawer.Root` component accepts a `direction` prop to control which side the - + @@ -500,6 +524,63 @@ The `Drawer.Root` component accepts a `direction` prop to control which side the `} /> +### Keep focus on the custom trigger element + +If a drawer is opened without the `Trigger` component (e.g., a dropdown item), focus will not return to the trigger element when the drawer is closed. To address this use `useCustomDialogTrigger` hook to register your custom trigger element. + + { + const [open, setOpen] = React.useState(false); + const { triggerRef, registerTrigger } = useCustomDialogTrigger(); + + const handleOpenDialog = () => { + registerTrigger(); + setOpen(true); + }; + +return ( + + <> + + + + + + + + + + + + + + + + + + + + + Content + Drawer opened from the custom trigger + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit +

+
+ + Close + +
+ +
+ + +)}`} +/> + ## API Reference ### `Root` @@ -595,17 +676,16 @@ Contains [`Drawer.Trigger`](#trigger) and [`Drawer.Content`](#content) component ### `Trigger` -An optional button that opens the drawer. +Used to open a drawer. Essential for registering the trigger element with the dialog focus manager. +If a drawer is opened without the `Trigger` component (e.g., a dropdown item), focus will not return to the trigger element when the drawer is closed. To address this use `useCustomDialogTrigger` hook to register your custom trigger element. @@ -637,7 +717,7 @@ Contains [`Drawer.Header`](#header), [`Drawer.Body`](#body) and [`Drawer.Footer` name: "size", description: "Width | height of the drawer.", required: false, - value: "'sm' | 'md' | 'lg'", + value: " '2xs' | 'xs' | 'sm' | 'md' | 'lg'", defaultValue: "sm", }, { diff --git a/apps/portal/src/content/docs/components/file-explorer.mdx b/apps/portal/src/content/docs/components/file-explorer.mdx new file mode 100644 index 0000000000..0dcb8a179a --- /dev/null +++ b/apps/portal/src/content/docs/components/file-explorer.mdx @@ -0,0 +1,656 @@ +--- +title: FileExplorer +description: FileExplorer component for displaying hierarchical file and folder structures +--- + +The `FileExplorer` component is used to display file and folder hierarchies in a collapsible/expandable structure. It supports features like active states, links, nested levels, and tooltips. + +import { DocsPage } from "@/components/docs-page"; + + { + const [expandedFolders, setExpandedFolders] = React.useState(['folder-1']); + +return + + + console.log("Open clicked"), + iconOnly: false, + size: "xs", + variant: "outline", + }, + { + iconName: "plus", + onClick: () => console.log("Open clicked"), + }, + ]} + level={1} + > + README.md + + package.json + + } +> + src + + + +}`} +/> + +## Usage + +```typescript jsx +import { FileExplorer } from '@harnessio/ui/components' + +//... + +const [expandedFolders, setExpandedFolders] = useState(['src']); + +return ( + + + index.ts + App.tsx + + } + > + src + + +) +``` + +## Basic File Explorer + +Create a simple file explorer with folders and files. The explorer automatically handles expand/collapse interactions. + + { + const [expanded, setExpanded] = React.useState(['root']); + +return + + + index.html + styles.css + script.js + + } +> + public + + + +}`} +/> + +## Nested Folders + +You can nest folders within folders to create deep hierarchies. + + { + const [expanded, setExpanded] = React.useState(['src', 'components']); + +return + + + + Button.tsx + Input.tsx + Modal.tsx + + } + > + components + + + helpers.ts + + constants.ts + + + } + > + utils + + index.ts + + } +> + src + + + +}`} +/> + +## With Active State + +Highlight the currently active file or folder using the `isActive` prop. + + { + const [expanded, setExpanded] = React.useState(['src']); + const [activeFile, setActiveFile] = React.useState('app-tsx'); + +return + + + setActiveFile("index-ts")} + > + index.ts + + setActiveFile("app-tsx")} + > + App.tsx + + setActiveFile("main-tsx")} + > + main.tsx + + + } +> + src + + + +}`} +/> + +## With Links + +Make files and folders navigable by providing the `link` prop. + + { + const [expanded, setExpanded] = React.useState(['docs']); + +return + + + + introduction.md + + + getting-started.md + + + api.md + + + } +> + docs + + + +}`} +/> + +## With Tooltips + +Add tooltips to files for additional information. + + { + const [expanded, setExpanded] = React.useState(['config']); + +return + + + + config.json + + + .env + + + tsconfig.json + + + } +> + config + + + +}`} +/> + +## Active Folder + +You can also highlight folders using the `isActive` prop. + + { + const [expanded, setExpanded] = React.useState(['src', 'components']); + +return + + + + Header.tsx + Footer.tsx + + } + > + components + + App.tsx + + } +> + src + + + +}`} +/> + +## Complex Example - Full Project Structure + +This comprehensive example demonstrates all features of the FileExplorer component including: + +- Multiple nested folder levels +- Active file highlighting +- Links for navigation +- Tooltips for additional context +- Mixed file and folder structures +- Click handlers for file selection + + { + const [expanded, setExpanded] = React.useState([ + 'project-root', + 'src', + 'components', + 'styles' + ]); + const [activeFile, setActiveFile] = React.useState('button-tsx'); + +return + + +{/* Source Directory */} + +{/* Components Folder */} + + setActiveFile('button-tsx')} +link="/src/components/button" +tooltip="Reusable button component" > +Button.tsx + + setActiveFile('input-tsx')} +link="/src/components/input" +tooltip="Form input component" > +Input.tsx + + setActiveFile('modal-tsx')} +link="/src/components/modal" +tooltip="Modal dialog component" > +Modal.tsx + + +index.ts + + +} > +components + + + {/* Styles Folder */} + + globals.css + variables.css + utils.css + + } + > + styles + + + {/* Utils Folder */} + + helpers.ts + api.ts + validation.ts + + } + > + utils + + + {/* Hooks Folder */} + + useAuth.ts + useForm.ts + useDebounce.ts + + } + > + hooks + + + {/* Root src files */} + setActiveFile('app-tsx')} + tooltip="Main application component" + > + App.tsx + + main.tsx + types.ts + + } + > + src + + + {/* Public Directory */} + + favicon.ico + index.html + manifest.json + + } + > + public + + + {/* Tests Directory */} + + Button.test.tsx + Input.test.tsx + setup.ts + + } + > + tests + + + {/* Root config files */} + package.json + tsconfig.json + vite.config.ts + .eslintrc.json + .env + README.md + + } + > + my-project + + + +}`} +/> + +## API Reference + +### Root Component Props + + void", + }, + { + name: "children", + description: "Child FolderItem and FileItem components.", + required: true, + value: "React.ReactNode", + }, + ]} +/> + +### FolderItem Component Props + + + +### FileItem Component Props + + void", + }, + { + name: "tooltip", + description: "Tooltip content to display when hovering over the file.", + required: false, + value: "React.ReactNode", + }, + ]} +/> + +## Best Practices + +### Level Numbering + +- **Folders**: Start at `level={0}` for root folders, increment by 1 for each nesting level +- **Files**: Start at `level={1}` for files directly under root folders, increment by 1 for each nesting level + +### State Management + +```typescript +// Initialize with folders you want expanded by default +const [expanded, setExpanded] = useState(['src', 'components']); + +// The Root component will handle the expand/collapse state + + ... + +``` + +### Performance Tips + +- Use `React.memo()` for FileItem components if you have a large file tree +- Keep track of active file in state to avoid unnecessary re-renders +- Use the `value` prop on FolderItem to uniquely identify folders for proper accordion behavior diff --git a/apps/portal/src/content/docs/components/icon.mdx b/apps/portal/src/content/docs/components/icon.mdx index 82d37deb1f..7c5a13f5c3 100644 --- a/apps/portal/src/content/docs/components/icon.mdx +++ b/apps/portal/src/content/docs/components/icon.mdx @@ -91,9 +91,9 @@ The `IconV2` component can be sized using the `size` prop, which applies predefi client:only code={` - + - + `} /> diff --git a/apps/portal/src/content/docs/components/pagination.mdx b/apps/portal/src/content/docs/components/pagination.mdx index a3aaf4bceb..c77370bf41 100644 --- a/apps/portal/src/content/docs/components/pagination.mdx +++ b/apps/portal/src/content/docs/components/pagination.mdx @@ -35,12 +35,16 @@ import { DocsPage } from "@/components/docs-page"; }`} /> -### Determinate Pagination with Hidden Page Numbers +### Determinate Pagination with Links { const [currentPage, setCurrentPage] = React.useState(1) + + const getPageLink = (page) => { + return '?page=' + page + } return (
@@ -48,32 +52,53 @@ import { DocsPage } from "@/components/docs-page"; totalItems={100} pageSize={10} currentPage={currentPage} + getPageLink={getPageLink} + /> +
+ ) + +}`} /> + +### Determinate Pagination with Page size select + + { + const [currentPage, setCurrentPage] = React.useState(1) + const [pageSize, setPageSize] = React.useState(10) + + return ( +
+
) }`} /> -### Determinate Pagination with Links +### Determinate Pagination with custom page size values. Default value [10, 25, 50]. { + const customOptions = [2, 5, 15] const [currentPage, setCurrentPage] = React.useState(1) - - const getPageLink = (page) => { - return '?page=' + page - } + const [pageSize, setPageSize] = React.useState(customOptions[0]) return (
) @@ -221,6 +246,20 @@ return ( required: false, value: "number", }, + { + name: "pageSizeOptions", + description: "Options for items per page select", + defaultValue: "[10, 25, 50]", + required: false, + value: "number[]", + }, + { + name: "onPageSizeChange", + description: + "Function to set the number of items per page (used with determinate pagination)", + required: false, + value: "(size: number) => void", + }, { name: "currentPage", description: "Current page number (used with determinate pagination)", @@ -228,8 +267,8 @@ return ( value: "number", }, { - name: "hidePageNumbers", - description: "Hide page numbers and only show prev/next buttons", + name: "showPageNumbers", + description: "Show page numbers instead of only prev/next buttons", required: false, value: "boolean", defaultValue: "false", diff --git a/apps/portal/src/content/docs/components/sidebar.mdx b/apps/portal/src/content/docs/components/sidebar.mdx index a83ec392dd..3a46765b2a 100644 --- a/apps/portal/src/content/docs/components/sidebar.mdx +++ b/apps/portal/src/content/docs/components/sidebar.mdx @@ -45,19 +45,35 @@ code={`() => { { title: 'Duplicate' }, { title: 'Delete' } ]} + actionButtons={[ + { iconName: 'plus' }, + { iconName: 'pin' }, + ]} /> @@ -76,6 +92,10 @@ code={`() => { icon="clock" title="Recent Activity" description="View your recent actions" + actionButtons={[ + { iconName: 'plus' }, + { iconName: 'pin' }, + ]} /> @@ -103,6 +123,10 @@ code={`() => { title="Settings" to="/settings" withRightIndicator + actionButtons={[ + { iconName: 'plus' }, + { iconName: 'pin' }, + ]} /> diff --git a/apps/portal/src/content/docs/components/spacer.mdx b/apps/portal/src/content/docs/components/spacer.mdx index f6ff3f0df1..705f16d1d7 100644 --- a/apps/portal/src/content/docs/components/spacer.mdx +++ b/apps/portal/src/content/docs/components/spacer.mdx @@ -19,27 +19,27 @@ The `Spacer` component is used to add vertical spacing between elements. code={`
Default size
-
+
-
+

Size 8
-
+
-
+

Size 16
-
+
-
+
`} diff --git a/apps/portal/src/content/docs/components/split-button.mdx b/apps/portal/src/content/docs/components/split-button.mdx index a22f1c9670..cdcff30437 100644 --- a/apps/portal/src/content/docs/components/split-button.mdx +++ b/apps/portal/src/content/docs/components/split-button.mdx @@ -25,11 +25,11 @@ import { Aside } from "@astrojs/starlight/components"; code={`
{/* Variants and Themes */}
-
Variants and Themes
+ Variants and Themes {/* Primary Variant */}
-
Primary Variant
+ Primary Variant
-
Secondary Variant
+ Secondary Variant
-
Outline Variant
+ Outline Variant
-
Radio Button Selection
+ + Radio Button Selection +
-
Loading State
+ + Loading State +
-
Disabled State
+ Disabled State
+## Field position + +The `StackedList.Field` component accepts the `right` prop, which positions the field on the right side of the `Item`. + + + + + + + +`} +/> + +## With pagination + +The `StackedList.Root` component can accept pagination props to enable pagination functionality within the list. + + console.log(page), pageSizeOptions: [5, 10, 20], onPageSizeChange: (size) => console.log(size) }}> + + + + + +`} +/> + ## API Reference ### `Root` @@ -273,6 +307,13 @@ Contains [`StackedList.Item`](#item) and [`StackedList.Field`](#field) component value: "'top'", defaultValue: "undefined", }, + { + name: "paginationProps", + description: "Props for the pagination component", + required: false, + value: "PaginationProps", + defaultValue: "undefined", + }, ]} /> diff --git a/apps/portal/src/content/docs/components/status-badge.mdx b/apps/portal/src/content/docs/components/status-badge.mdx index 389f89cb84..2b73b9b169 100644 --- a/apps/portal/src/content/docs/components/status-badge.mdx +++ b/apps/portal/src/content/docs/components/status-badge.mdx @@ -12,7 +12,7 @@ import { DocsPage } from "../../../components/docs-page"; client:only code={`
-
Primary (Default)
+ Primary (Default) Default Success Warning @@ -20,7 +20,7 @@ import { DocsPage } from "../../../components/docs-page"; Info Merged -
Secondary
+ Secondary Default Success Warning @@ -28,7 +28,7 @@ import { DocsPage } from "../../../components/docs-page"; Info Merged -
Outline
+ Outline Default Success Warning @@ -36,7 +36,7 @@ import { DocsPage } from "../../../components/docs-page"; Info Merged -
Ghost
+ Ghost Default Success Warning @@ -44,7 +44,7 @@ import { DocsPage } from "../../../components/docs-page"; Info Merged -
Status
+ Status Default Success Warning @@ -52,7 +52,7 @@ import { DocsPage } from "../../../components/docs-page"; Info Merged -
Status with Pulse
+ Status with Pulse Default Success Warning @@ -64,7 +64,7 @@ import { DocsPage } from "../../../components/docs-page";
-
Primary (Default)
+ Primary (Default) Default Success Warning @@ -72,7 +72,7 @@ import { DocsPage } from "../../../components/docs-page"; Info Merged -
Secondary
+ Secondary Default Success Warning @@ -80,7 +80,7 @@ import { DocsPage } from "../../../components/docs-page"; Info Merged -
Outline
+ Outline Default Success Warning @@ -88,7 +88,7 @@ import { DocsPage } from "../../../components/docs-page"; Info Merged -
Ghost
+ Ghost Default Success Warning @@ -96,7 +96,7 @@ import { DocsPage } from "../../../components/docs-page"; Info Merged -
Status
+ Status Default Success Warning @@ -104,7 +104,7 @@ import { DocsPage } from "../../../components/docs-page"; Info Merged -
Status with Pulse
+ Status with Pulse Default Success Warning diff --git a/apps/portal/src/content/docs/components/table-v2.mdx b/apps/portal/src/content/docs/components/table-v2.mdx index c32d4ce61a..2a44013189 100644 --- a/apps/portal/src/content/docs/components/table-v2.mdx +++ b/apps/portal/src/content/docs/components/table-v2.mdx @@ -212,7 +212,7 @@ import { Aside } from "@astrojs/starlight/components";
Table with Submenu and Pagination
- + console.log(page), pageSizeOptions: [5, 10, 20], onPageSizeChange: (size) => console.log(size) }}> Name @@ -274,12 +274,6 @@ import { Aside } from "@astrojs/starlight/components";
- {}} - />
`} /> @@ -438,6 +432,13 @@ The root component for the table. value: "boolean", defaultValue: "false", }, + { + name: "paginationProps", + description: "Props for the pagination component", + required: false, + value: "PaginationProps", + defaultValue: "undefined", + }, ]} /> diff --git a/apps/portal/src/content/docs/components/tabs.mdx b/apps/portal/src/content/docs/components/tabs.mdx index 99b08d4d99..a624488131 100644 --- a/apps/portal/src/content/docs/components/tabs.mdx +++ b/apps/portal/src/content/docs/components/tabs.mdx @@ -245,7 +245,7 @@ When icons, logos, and counters aren't sufficient, you can use custom children f Documents - (2.3 GB) + (2.3 GB) @@ -320,7 +320,7 @@ Both `Tabs.Root` and `Tabs.NavRoot` can be controlled by passing `value` and `on - + Active tab: {activeTab}
@@ -337,7 +337,7 @@ You can customize the active tab styling using the `activeClassName` prop on `Ta code={` Custom Active 1 Custom Active 2 diff --git a/apps/portal/src/content/docs/components/tag.mdx b/apps/portal/src/content/docs/components/tag.mdx index b7fc555ac6..4e7daed118 100644 --- a/apps/portal/src/content/docs/components/tag.mdx +++ b/apps/portal/src/content/docs/components/tag.mdx @@ -15,7 +15,9 @@ import { DocsPage } from "@/components/docs-page"; {/* Themes */}
-
Themes
+ + Themes +
{[ "gray", @@ -45,7 +47,9 @@ import { DocsPage } from "@/components/docs-page"; {/* Variants */}
-
Variants
+ + Variants +
@@ -55,7 +59,9 @@ import { DocsPage } from "@/components/docs-page"; {/* Sizes */}
-
Sizes
+ + Sizes +
@@ -74,7 +80,9 @@ import { DocsPage } from "@/components/docs-page"; {/* Rounded Tags */}
-
Rounded Tags
+ + Rounded Tags +
-
Split Tags
+ + Split Tags +
@@ -108,7 +118,9 @@ import { DocsPage } from "@/components/docs-page"; {/* Tags with Icon */}
-
Tags with Icon
+ + Tags with Icon +
@@ -132,7 +144,9 @@ import { DocsPage } from "@/components/docs-page"; {/* Tags with Custom Icon */}
-
Tags with Custom Icon
+ + Tags with Custom Icon +
-
Tags with Reset
+ + Tags with Reset +
-
Tags with Icon and Reset
+ Tags with Icon and Reset
` element. Other va dummyText
- caption-single-line-soft: - dummyText + caption-single-line-light: + dummyText
-
- - {/* Colors -
- inherit: - dummyText - -
-
- foreground-1: - dummyText -
-
- foreground-2: - dummyText -
-
- foreground-3: - dummyText -
-
- state-disabled: - dummyText -
-
- success: - dummyText -
-
- warning: - dummyText -
-
- danger: - dummyText -
*/} -
`.replaceAll('dummyText', `Consistency in typography helps users navigate content with ease. Always use hierarchy to guide attention and improve readability.`)} /> @@ -300,7 +263,7 @@ The `lineClamp` prop controls how many lines of text are shown before truncating defaultValue: "body-normal", required: false, value: - "'heading-hero' | 'heading-section' | 'heading-subsection' | 'heading-base' | 'heading-small' | 'body-normal' | 'body-single-line-normal' | 'body-strong' | 'body-single-line-strong' | 'body-code' | 'caption-normal' | 'caption-soft' | 'caption-single-line-normal' | 'caption-single-line-soft'", + "'heading-hero' | 'heading-section' | 'heading-subsection' | 'heading-base' | 'heading-small' | 'body-normal' | 'body-single-line-normal' | 'body-strong' | 'body-single-line-strong' | 'body-code' | 'caption-normal' | 'caption-soft' | 'caption-single-line-normal' | 'caption-single-line-light'", }, { name: "color", diff --git a/apps/portal/src/content/docs/components/time-ago-card.mdx b/apps/portal/src/content/docs/components/time-ago-card.mdx index de80cf1b55..b1e7d98ae7 100644 --- a/apps/portal/src/content/docs/components/time-ago-card.mdx +++ b/apps/portal/src/content/docs/components/time-ago-card.mdx @@ -90,6 +90,12 @@ return ( required: false, value: "'TextProps'", }, + { + name: "tooltipProps", + description: "Props for the tooltip element.", + required: false, + value: "'TooltipProps'", + }, { name: "triggerClassName", description: "Class names for the trigger element.", diff --git a/apps/portal/src/content/docs/components/toast.mdx b/apps/portal/src/content/docs/components/toast.mdx index ff5126e53a..77e3c2cddf 100644 --- a/apps/portal/src/content/docs/components/toast.mdx +++ b/apps/portal/src/content/docs/components/toast.mdx @@ -4,332 +4,312 @@ description: Toast component beta: true --- -The `Toast` component is used to display notifications to the user. The `Toast.Root` component can be used either controlled or uncontrolled. When used uncontrolled, the `Toast.Root` component automatically handles the open state of the toast. When used controlled, the `Toast.Root` component can be controlled by providing the `open` and `onOpenChange` props. - +import { Code, Card, Aside } from "@astrojs/starlight/components"; import { DocsPage } from "../../../components/docs-page"; - { - const [show, setShow] = React.useState(false) - - return ( - - +`toast` is a toast component that is used to display notifications to the user. It is handled by `Toaster` component, that is added in the root of the app. - {show && ( - - Toast title - Toast description - - - )} - - - ) +To show a simple toast, use the `toast` function. -}`} + + toast({ + title: 'Toast title', + description: Array.from({ length: Math.floor(Math.random() * 1000) + 1 }) + .map(() => 'a') + .join(''), + options: { + duration: 3000, + action: { + label: 'Undo', + onClick: () => { + console.log('Undo') + } + } + } + }) + } + > + Show toast + `} /> -## Usage - -```typescript jsx -import { Toast } from '@harnessio/ui/components' - -//... - -return ( - {/* typically placed at the root of the app */} - - Toast title - Toast description - - - - -) -``` + { + console.log('Undo') + } + } + } + }) + `} + lang="typescript" +/> -## Anatomy +## Toast types -All parts of the `Toast` component can be imported and composed as required. +`toast` can be used to show different types of toasts. -```typescript jsx - - - - - - - - - -``` +
    +
  1. - Default toast (`toast`)
  2. +
  3. - Success toast (`toast.success`)
  4. +
  5. - Danger toast (`toast.danger`)
  6. +
  7. - Info toast (`toast.info`)
  8. +
  9. - Loading toast (`toast.loading`)
  10. +
  11. - Promise toast (`toast.promise`)
  12. +
-## API Reference + -### `Provider` +
    +
  1. Maximum number of toasts that can be displayed at a time is 3.
  2. +
  3. - If duration is not provided, the toast will be closed after 10 seconds by default.
  4. +
  5. - Close button in toast will be hidden only if dismissible is set to false in options. By default, it is set to true.
  6. +
  7. - Action button can be added to toast by providing action in options.
  8. +
  9. - Clicking of action button will execute the callback function provided in action and will not close the toast by default. If you want to close the toast after clicking the action button, you can use toast.dismiss in the callback function.
  10. +
  11. - Promise toast and Loading toast cannot be dismissed by the user, it will be closed after the promise is resolved or rejected (Promise toast) or when the work in progress task is completed (Loading toast).
  12. +
  13. - Promise toast and Loading toast cannot display a description
  14. +
+
-The `Provider` component is used to provide the Toast context to the rest of the app. It is typically placed at the root of the app. +## Usage -```typescript jsx - - {/* Toast roots and viewport */} - -``` +### Success toast - + toast.success({ + title: 'Toast title', + description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Magni magnam quibusdam fugit saepe voluptates aperiam perspiciatis ipsam! Ad cupiditate, tempora aspernatur a, veritatis, rem maxime est aliquam natus iure vero. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dignissimos quis reprehenderit quas! Praesentium, delectus perferendis? Dicta, reiciendis? Sint ipsa necessitatibus natus, magni, delectus consequuntur, aperiam asperiores in quasi commodi doloribus?", + options: { + duration: 5000, + action: { + label: 'Undo', + onClick: () => { + console.log('Undo') + } + } + } + }) + } + > + Show success toast + `} /> -### `Root` +### Danger toast -The `Root` component is used to wrap the content of the toast. The `Root` component can be used either controlled or uncontrolled. When used uncontrolled, the `Root` component automatically handles the open state of the toast. When used controlled, the `Root` component can be controlled by providing the `open` and `onOpenChange` props. + + toast.danger({ + title: 'Toast title', + description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Magni magnam quibusdam fugit saepe voluptates aperiam perspiciatis ipsam! Ad cupiditate, tempora aspernatur a, veritatis, rem maxime est aliquam natus iure vero.", + options: { + duration: 5000, + action: { + label: 'Undo', + onClick: () => { + console.log('Undo') + } + } + } + }) + } + > + Show danger toast + `} +/> -```typescript jsx - - Toast title - Toast description - - -``` +### Info toast - void", - }, - { - name: "defaultOpen", - description: "Whether the toast is open by default", - required: false, - value: "boolean", - }, - { - name: "forceMount", - description: "Whether the toast should be mounted even if it is not open", - required: false, - value: "boolean", - }, - { - name: "className", - description: "Custom class name", - required: false, - value: "string", - }, - ]} + + toast.info({ + title: 'Toast title', + description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Magni magnam quibusdam fugit saepe voluptates aperiam perspiciatis ipsam! Ad cupiditate, tempora aspernatur a, veritatis, rem maxime est aliquam natus iure vero.", + options: { + duration: 5000, + } + }) + } + > + Show info toast + `} /> -### `Title` +### Loading toast -The `Title` component is used to display the title of the toast. + -```typescript jsx - - Toast title - -``` - - { + const _toastId = toast.loading({ + title: 'Loading data...', + options: { + action: { + label: 'Close', + onClick: () => { + toast.dismiss(_toastId) + } + } + } + }) + }} + > + Show loading toast + + `} /> -### `Description` +### Promise toast -The `Description` component is used to display the description of the toast. +Promise toast accepts a **promise** as its **first argument** and an **options object** as its **second argument**. -```typescript jsx - - Toast description - -``` + - ( + +) +`} /> -### `Close` - -The `Close` component is used to close the toast. +### Toast params -```typescript jsx - - - -``` +Toast params are the parameters that are passed to the toast function. void", + value: "ReactNode", }, - ]} -/> - -### `Action` - -The `Action` component is used to display a call-to-action button in the toast. - -```typescript jsx - - - -``` - - void", + value: "string", }, ]} /> -### `Viewport` - -The `Viewport` component is used to display the toast in the viewport. +### Toast options -```typescript jsx - -``` +Toast options are the configuration object that are passed to the toast parameter. diff --git a/apps/portal/src/content/docs/components/tree-view.mdx b/apps/portal/src/content/docs/components/tree-view.mdx new file mode 100644 index 0000000000..6411b0ed75 --- /dev/null +++ b/apps/portal/src/content/docs/components/tree-view.mdx @@ -0,0 +1,1132 @@ +--- +title: TreeView +description: TreeView component for displaying hierarchical data structures +--- + +The `TreeView` component is used to display hierarchical data in an expandable/collapsible tree structure. It supports features like selection, status indicators, execution details, and RTL/LTR text direction. + +import { DocsPage } from "@/components/docs-page"; + + { + const basicTreeData = [ + { + id: '1', + name: 'Root Folder', + status: 'success', + children: [ + { id: '1-1', name: 'Child File 1', status: 'success' }, + { id: '1-2', name: 'Child File 2', status: 'running' } + ] + } + ]; + +return + + + + Child File 1 + + + Child File 2 + + + + +}`} +/> + +## Usage + +```typescript jsx +import { Tree, Folder, File, CollapseButton, type TreeViewElement } from '@harnessio/ui/components' +import { ExecutionState } from '@harnessio/ui/views' + +//... + +return ( + + + + File 1 + + + +) +``` + +## Basic Tree Structure + +Create a simple tree with folders and files. The tree automatically handles expand/collapse interactions. + + { + const simpleTree = [ + { + id: 'folder-1', + name: 'Documents', + status: 'success', + children: [ + { id: 'file-1', name: 'Resume.pdf', status: 'success' }, + { id: 'file-2', name: 'Cover Letter.docx', status: 'success' } + ] + }, + { + id: 'folder-2', + name: 'Projects', + status: 'running', + children: [ + { + id: 'folder-2-1', + name: 'Web App', + status: 'running', + children: [ + { id: 'file-3', name: 'index.html', status: 'success' }, + { id: 'file-4', name: 'styles.css', status: 'running' } + ] + } + ] + } + ]; + +return + + + + Resume.pdf + + + Cover Letter.docx + + + + + + index.html + + + styles.css + + + + + +}`} +/> + +## With Status Indicators + +The TreeView component displays status icons based on the `ExecutionState`. It supports states like `SUCCESS`, `RUNNING`, `FAILURE`, `PENDING`, and more. + + { + const statusTree = [ + { + id: 'build-1', + name: 'Build Pipeline', + status: 'running', + duration: '2m 30s', + children: [ + { id: 'test-1', name: 'Unit Tests', status: 'success', duration: '45s' }, + { id: 'test-2', name: 'Integration Tests', status: 'running', duration: '1m 15s' }, + { id: 'test-3', name: 'E2E Tests', status: 'pending' } + ] + }, + { + id: 'build-2', + name: 'Deploy Pipeline', + status: 'failure', + duration: '3m 10s', + children: [ + { id: 'deploy-1', name: 'Staging Deploy', status: 'success', duration: '2m' }, + { id: 'deploy-2', name: 'Production Deploy', status: 'failure', duration: '1m 10s' } + ] + } + ]; + +return + + + + Unit Tests + + + Integration Tests + + + E2E Tests + + + + + Staging Deploy + + + Production Deploy + + + + +}`} +/> + +## With Initial Selection + +You can specify an initial selected item using the `initialSelectedId` prop. The tree will automatically expand parent folders to show the selected item. + + { + const selectionTree = [ + { + id: 'root', + name: 'Root', + status: 'success', + children: [ + { + id: 'nested-folder', + name: 'Nested Folder', + status: 'success', + children: [ + { id: 'target-file', name: 'Target File (Selected)', status: 'success' }, + { id: 'other-file', name: 'Other File', status: 'success' } + ] + } + ] + } + ]; + +return + + + + + Target File (Selected) + + + Other File + + + + + +}`} +/> + +## With Initial Expanded Items + +Control which folders are initially expanded using the `initialExpendedItems` prop. + + { + const expandedTree = [ + { + id: 'folder-a', + name: 'Folder A (Expanded)', + status: 'success', + children: [ + { id: 'file-a1', name: 'File A1', status: 'success' }, + { id: 'file-a2', name: 'File A2', status: 'success' } + ] + }, + { + id: 'folder-b', + name: 'Folder B (Collapsed)', + status: 'success', + children: [ + { id: 'file-b1', name: 'File B1', status: 'success' } + ] + } + ]; + +return + + + File A1 + File A2 + + + File B1 + + + +}`} +/> + +## Non-Selectable Items + +You can make specific items non-selectable by setting `isSelectable: false` on the element. + + { + const nonSelectableTree = [ + { + id: 'config', + name: 'Configuration (Non-selectable)', + status: 'success', + isSelectable: false, + children: [ + { id: 'config-1', name: 'app.config.js', status: 'success' }, + { id: 'config-2', name: 'env.config.js', status: 'success' } + ] + }, + { + id: 'src', + name: 'Source Files', + status: 'success', + children: [ + { id: 'src-1', name: 'index.ts', status: 'success' } + ] + } + ]; + +return + + + + app.config.js + + + env.config.js + + + + + index.ts + + + + +}`} +/> + +## With Collapse Button + +Add a collapse/expand all button using the `CollapseButton` component. + + { + const collapseTreeElements = [ + { + id: 'module-1', + name: 'Module 1', + status: 'success', + children: [ + { id: 'file-1', name: 'component.tsx', status: 'success' }, + { id: 'file-2', name: 'styles.css', status: 'success' } + ] + }, + { + id: 'module-2', + name: 'Module 2', + status: 'success', + children: [ + { id: 'file-3', name: 'utils.ts', status: 'success' }, + { id: 'file-4', name: 'types.ts', status: 'success' } + ] + } + ]; + +return
+ + + + + component.tsx + + + styles.css + + + + + utils.ts + + + types.ts + + + + + + + +
+}`} +/> + +## RTL Support + +The TreeView component supports right-to-left (RTL) text direction using the `dir` prop. + + { + const rtlTree = [ + { + id: 'rtl-1', + name: 'مجلد رئيسي', + status: 'success', + children: [ + { id: 'rtl-1-1', name: 'ملف 1', status: 'success' }, + { id: 'rtl-1-2', name: 'ملف 2', status: 'running' } + ] + } + ]; + +return + + + + ملف 1 + + + ملف 2 + + + + +}`} +/> + +## Complex Example - Full-Featured Pipeline Execution Tree + +This comprehensive example demonstrates all features of the TreeView component including: + +- Multiple nested levels +- Various execution states with status icons +- Duration display for each item +- Initial selection and expansion +- Collapse/expand all functionality +- Mix of selectable and non-selectable items +- Real-world CI/CD pipeline structure + + { + const pipelineExecutionTree = [ + { + id: 'pipeline-main', + name: 'Main Pipeline', + status: 'running', + duration: '15m 45s', + children: [ + { + id: 'stage-build', + name: 'Build Stage', + status: 'success', + duration: '5m 20s', + children: [ + { + id: 'job-compile', + name: 'Compile', + status: 'success', + duration: '2m 15s', + children: [ + { id: 'step-install', name: 'Install Dependencies', status: 'success', duration: '45s' }, + { id: 'step-compile', name: 'Run Compiler', status: 'success', duration: '1m 30s' } + ] + }, + { + id: 'job-lint', + name: 'Lint & Format', + status: 'success', + duration: '1m 30s', + children: [ + { id: 'step-eslint', name: 'ESLint Check', status: 'success', duration: '50s' }, + { id: 'step-prettier', name: 'Prettier Check', status: 'success', duration: '40s' } + ] + }, + { + id: 'job-build', + name: 'Build Application', + status: 'success', + duration: '1m 35s' + } + ] + }, + { + id: 'stage-test', + name: 'Test Stage', + status: 'running', + duration: '8m 15s', + children: [ + { + id: 'job-unit', + name: 'Unit Tests', + status: 'success', + duration: '3m 20s', + children: [ + { id: 'step-jest', name: 'Jest Tests', status: 'success', duration: '2m 45s' }, + { id: 'step-coverage', name: 'Coverage Report', status: 'success', duration: '35s' } + ] + }, + { + id: 'job-integration', + name: 'Integration Tests', + status: 'running', + duration: '4m 55s', + children: [ + { id: 'step-setup-db', name: 'Setup Test DB', status: 'success', duration: '1m 10s' }, + { id: 'step-api-tests', name: 'API Tests', status: 'running', duration: '2m 30s' }, + { id: 'step-cleanup', name: 'Cleanup', status: 'pending' } + ] + }, + { + id: 'job-e2e', + name: 'E2E Tests', + status: 'pending', + isSelectable: false + } + ] + }, + { + id: 'stage-security', + name: 'Security Scan', + status: 'pending', + isSelectable: false, + children: [ + { id: 'job-sast', name: 'SAST Scan', status: 'pending' }, + { id: 'job-dependency', name: 'Dependency Check', status: 'pending' } + ] + }, + { + id: 'stage-deploy', + name: 'Deploy Stage', + status: 'pending', + isSelectable: false, + children: [ + { + id: 'job-staging', + name: 'Deploy to Staging', + status: 'pending', + children: [ + { id: 'step-build-image', name: 'Build Docker Image', status: 'pending' }, + { id: 'step-push-registry', name: 'Push to Registry', status: 'pending' }, + { id: 'step-deploy-k8s', name: 'Deploy to K8s', status: 'pending' } + ] + }, + { + id: 'job-production', + name: 'Deploy to Production', + status: 'pending', + isSelectable: false + } + ] + } + ] + }, + { + id: 'pipeline-hotfix', + name: 'Hotfix Pipeline', + status: 'failure', + duration: '4m 12s', + children: [ + { + id: 'hotfix-build', + name: 'Quick Build', + status: 'success', + duration: '1m 45s' + }, + { + id: 'hotfix-test', + name: 'Smoke Tests', + status: 'failure', + duration: '2m 27s', + children: [ + { id: 'hotfix-test-1', name: 'Critical Path Test', status: 'failure', duration: '2m 27s' } + ] + } + ] + }, + { + id: 'pipeline-scheduled', + name: 'Scheduled Maintenance', + status: 'skipped', + duration: '--', + children: [ + { id: 'cleanup-logs', name: 'Cleanup Logs', status: 'skipped' }, + { id: 'backup-db', name: 'Backup Database', status: 'skipped' } + ] + } + ]; + +return
+ + + + + + + Install Dependencies + + + Run Compiler + + + + + ESLint Check + + + Prettier Check + + + + Build Application + + + + + + Jest Tests + + + Coverage Report + + + + + Setup Test DB + + + API Tests + + + Cleanup + + + + E2E Tests + + + + + SAST Scan + + + Dependency Check + + + + + + Build Docker Image + + + Push to Registry + + + Deploy to K8s + + + + Deploy to Production + + + + + + Quick Build + + + + Critical Path Test + + + + + + Cleanup Logs + + + Backup Database + + + + + + + +
+}`} +/> + +## API Reference + +### TreeViewElement Type + +```typescript +type TreeViewElement = { + id: string; // Unique identifier for the tree item + name: string; // Display name of the item + isSelectable?: boolean; // Whether the item can be selected (default: true) + children?: TreeViewElement[]; // Nested child elements + status: ExecutionState; // Execution status (see ExecutionState enum) + duration?: string; // Formatted duration string (e.g., "2m 30s") +}; +``` + +### ExecutionState Enum + +```typescript +enum ExecutionState { + PENDING = "pending", + RUNNING = "running", + SUCCESS = "success", + FAILURE = "failure", + ERROR = "error", + SKIPPED = "skipped", + KILLED = "killed", + BLOCKED = "blocked", + WAITING_ON_DEPENDENCIES = "waiting_on_dependencies", + UNKNOWN = "unknown", +} +``` + +### Tree Component Props + + + +### Folder Component Props + + + +### File Component Props + + void", + }, + ]} +/> + +### CollapseButton Component Props + + + +## Helper Utility: Rendering Tree Elements + +You can create a helper utility to recursively render tree elements from data: + +```typescript jsx +import { ReactNode } from 'react' +import { File, Folder, type TreeViewElement } from '@harnessio/ui/components' + +interface RenderTreeElementProps { + element: TreeViewElement + handleClick?: (params: { parentNode: TreeViewElement | null; childNode: TreeViewElement }) => void + parentElement?: TreeViewElement | null + level?: number +} + +// Main render function +export const renderTree = ( + elements: TreeViewElement[], + handleClick?: (params: { parentNode: TreeViewElement | null; childNode: TreeViewElement }) => void +): ReactNode => { + if (elements.length === 0) return [] + return elements.map(element => ( +
+ {renderTreeElement({ element, handleClick, parentElement: null })} +
+ )) +} + +// Render folder with children +const renderTreeFolder = ({ element: folderElement, handleClick, level = 0 }: RenderTreeElementProps): ReactNode => { + return ( + + {folderElement.children?.map(childElement => ( +
+ {renderTreeElement({ + parentElement: folderElement, + element: childElement, + handleClick, + level: level + 1 + })} +
+ ))} +
+ ) +} + +// Render file (leaf node) +const renderTreeFile = ({ + element: fileElement, + handleClick, + parentElement, + level = 0 +}: RenderTreeElementProps): ReactNode => { + return ( + handleClick?.({ parentNode: parentElement, childNode: fileElement })} + level={level} + > + {fileElement.name} + + ) +} + +// Decide whether to render folder or file +const renderTreeElement = ({ + element, + handleClick, + parentElement, + level = 0 +}: RenderTreeElementProps): ReactNode => { + if (element.children && element.children.length > 0) { + return renderTreeFolder({ element, handleClick, parentElement, level }) + } + return renderTreeFile({ element, handleClick, parentElement, level }) +} + +// Usage example: +const treeData: TreeViewElement[] = [ + { + id: '1', + name: 'Folder', + status: 'success', + children: [ + { id: '1-1', name: 'File 1', status: 'success' } + ] + } +] + + + {renderTree(treeData, ({ parentNode, childNode }) => { + console.log('Selected:', childNode.name) + })} + +``` diff --git a/apps/portal/src/content/docs/foundations/typography.mdx b/apps/portal/src/content/docs/foundations/typography.mdx index 4a6e552500..480730f5d6 100644 --- a/apps/portal/src/content/docs/foundations/typography.mdx +++ b/apps/portal/src/content/docs/foundations/typography.mdx @@ -94,8 +94,8 @@ Captions are used for auxiliary information, labels, and metadata. Consistency in typography helps users navigate content with ease.
- caption-single-line-soft: - Consistency in typography helps users navigate content with ease. + caption-single-line-light: + Consistency in typography helps users navigate content with ease.
`} /> diff --git a/packages/core-design-system/design-tokens/components/desktop/base/drawer.json b/packages/core-design-system/design-tokens/components/desktop/base/drawer.json index fcf5a43c6d..caa3681672 100644 --- a/packages/core-design-system/design-tokens/components/desktop/base/drawer.json +++ b/packages/core-design-system/design-tokens/components/desktop/base/drawer.json @@ -41,4 +41,4 @@ "$value": "{size.8}" } } -} \ No newline at end of file +} diff --git a/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json b/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json index 997f042524..50d779e0fc 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/default-deuteranopia.json @@ -2776,7 +2776,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json b/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json index 170b772497..cb32351e34 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/default-protanopia.json @@ -2776,7 +2776,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json b/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json index b973d8fa73..19bcd29664 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/default-tritanopia.json @@ -2776,7 +2776,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/dark/default.json b/packages/core-design-system/design-tokens/mode/dark/default.json index ac737365bf..5631764cfa 100644 --- a/packages/core-design-system/design-tokens/mode/dark/default.json +++ b/packages/core-design-system/design-tokens/mode/dark/default.json @@ -176,17 +176,17 @@ }, "bg": { "$type": "color", - "$value": "{blue.950}", + "$value": "{blue.900}", "$description": "Background color for subdued gray surfaces." }, "bg-hover": { "$type": "color", - "$value": "{blue.900}", + "$value": "{blue.850}", "$description": "Hover state background color for interactive brand subtle surfaces." }, "bg-selected": { "$type": "color", - "$value": "{blue.900}", + "$value": "{blue.850}", "$description": "Selected state background color for active brand subtle items." } }, @@ -198,7 +198,7 @@ }, "bg": { "$type": "color", - "$value": "{blue.900}", + "$value": "{blue.1000}", "$description": "Background color for primary brand items with minimum emphasis." }, "border": { @@ -208,12 +208,12 @@ }, "bg-hover": { "$type": "color", - "$value": "{blue.800}", + "$value": "{blue.950}", "$description": "Hover state background color for interactive brand with minimum emphasis surfaces." }, "bg-selected": { "$type": "color", - "$value": "{blue.800}", + "$value": "{blue.900}", "$description": "Selected state background color for active brand with minimum emphasis items." } } @@ -2085,7 +2085,7 @@ "y": "0", "blur": "0", "spread": "4", - "color": "{set.brand.outline.bg}", + "color": "{set.brand.secondary.bg}", "type": "dropShadow" } }, @@ -2804,7 +2804,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json b/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json index 2b3bc3de79..b0b350f33c 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer-deuteranopia.json @@ -2767,7 +2767,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json b/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json index b331a1c1b7..a2d754772e 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer-protanopia.json @@ -2767,7 +2767,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json b/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json index 9eb2a2c026..795cfeb58b 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer-tritanopia.json @@ -2767,7 +2767,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/dark/dimmer.json b/packages/core-design-system/design-tokens/mode/dark/dimmer.json index 893ad03e41..f15064750f 100644 --- a/packages/core-design-system/design-tokens/mode/dark/dimmer.json +++ b/packages/core-design-system/design-tokens/mode/dark/dimmer.json @@ -2767,7 +2767,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json index 5b96bba08c..f7313753ec 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast-deuteranopia.json @@ -2776,7 +2776,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json index 025d872f8b..1add5b5c42 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast-protanopia.json @@ -2776,7 +2776,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json index e280144965..c10f126254 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast-tritanopia.json @@ -2776,7 +2776,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/dark/high-contrast.json b/packages/core-design-system/design-tokens/mode/dark/high-contrast.json index 6527dfcf0c..1002f0cfc6 100644 --- a/packages/core-design-system/design-tokens/mode/dark/high-contrast.json +++ b/packages/core-design-system/design-tokens/mode/dark/high-contrast.json @@ -2776,7 +2776,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json b/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json index 567a91c650..bbf52be9b8 100644 --- a/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/light/default-deuteranopia.json @@ -2747,7 +2747,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/light/default-protanopia.json b/packages/core-design-system/design-tokens/mode/light/default-protanopia.json index 906487379c..ab083b93ce 100644 --- a/packages/core-design-system/design-tokens/mode/light/default-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/default-protanopia.json @@ -2747,7 +2747,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json b/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json index d1c3eef442..36fdfdeda5 100644 --- a/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/default-tritanopia.json @@ -2747,7 +2747,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/light/default.json b/packages/core-design-system/design-tokens/mode/light/default.json index 9243b7e2ec..64a806a9f9 100644 --- a/packages/core-design-system/design-tokens/mode/light/default.json +++ b/packages/core-design-system/design-tokens/mode/light/default.json @@ -2076,7 +2076,7 @@ "y": "0", "blur": "0", "spread": "4", - "color": "{set.brand.outline.bg}", + "color": "{set.brand.secondary.bg}", "type": "dropShadow" } }, @@ -2804,7 +2804,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json b/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json index 067e475448..cac8851baa 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer-deuteranopia.json @@ -2756,7 +2756,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json b/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json index 5850228a6f..6191371820 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer-protanopia.json @@ -2756,7 +2756,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json b/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json index 80bbea6ea3..f465fe54f5 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer-tritanopia.json @@ -2756,7 +2756,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/light/dimmer.json b/packages/core-design-system/design-tokens/mode/light/dimmer.json index 99e5d46219..64a57d647b 100644 --- a/packages/core-design-system/design-tokens/mode/light/dimmer.json +++ b/packages/core-design-system/design-tokens/mode/light/dimmer.json @@ -2738,7 +2738,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json b/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json index 6d611f7c31..e00b74beae 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast-deuteranopia.json @@ -2747,7 +2747,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json b/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json index 35bc6f75be..a486ca51d5 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast-protanopia.json @@ -2747,7 +2747,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json b/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json index a2b0560a39..9e6e014f4f 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast-tritanopia.json @@ -2747,7 +2747,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/core-design-system/design-tokens/mode/light/high-contrast.json b/packages/core-design-system/design-tokens/mode/light/high-contrast.json index 0fb5cfaae6..1f1df130fd 100644 --- a/packages/core-design-system/design-tokens/mode/light/high-contrast.json +++ b/packages/core-design-system/design-tokens/mode/light/high-contrast.json @@ -2747,7 +2747,7 @@ "disabled": { "opacity": { "$type": "opacity", - "$value": "{opacity.50}" + "$value": "{opacity.60}" } }, "img": { diff --git a/packages/forms/package.json b/packages/forms/package.json index 3d4d687dfe..dacdc2be32 100644 --- a/packages/forms/package.json +++ b/packages/forms/package.json @@ -1,6 +1,6 @@ { "name": "@harnessio/forms", - "version": "0.1.3", + "version": "0.1.5", "description": "Harness Forms Library", "scripts": { "playground": "vite dev --config vite.config.playground.ts", diff --git a/packages/forms/src/core/utils/transform-utils.tsx b/packages/forms/src/core/utils/transform-utils.tsx index 137c339772..b87d07a015 100644 --- a/packages/forms/src/core/utils/transform-utils.tsx +++ b/packages/forms/src/core/utils/transform-utils.tsx @@ -1,6 +1,7 @@ import { cloneDeep, get, isArray, pick, set, unset } from 'lodash-es' import { IFormDefinition, IInputDefinition } from '../../types/types' +import { removeTemporaryFieldsValue } from './temporary-field-utils' type TransformItem = { path: string @@ -101,13 +102,20 @@ export function getTransformers(formDefinition: IFormDefinition): TransformItem[ export function unsetHiddenInputsValues( formDefinition: IFormDefinition, values: Record, - metadata?: any + metadata?: any, + options: { preserve?: string[] } = {} ): Record { + const { preserve } = options + const flattenInputs = flattenInputsRec(formDefinition.inputs) const retValues = cloneDeep(values) flattenInputs.forEach(input => { + if (preserve && preserve.includes(input.path)) { + return + } + if (!!input.isVisible && !input.isVisible(values, metadata)) { unset(retValues, input.path) } @@ -115,3 +123,26 @@ export function unsetHiddenInputsValues( return retValues } + +/** Convert data to form model */ +export function convertDataToFormModel( + inputData: Record, + formDefinition: IFormDefinition +): Record { + const transformers = getTransformers(formDefinition) + return inputTransformValues(inputData, transformers) +} + +/** Convert form model to data */ +export function convertFormModelToData( + formData: Record, + formDefinition: IFormDefinition, + metadata?: any, + options: { preserve?: string[] } = {} +): Record { + const transformers = getTransformers(formDefinition) + let data = unsetHiddenInputsValues(formDefinition, formData, metadata, options) + data = outputTransformValues(data, transformers) + data = removeTemporaryFieldsValue(data) + return data +} diff --git a/packages/ui/locales/en/component.json b/packages/ui/locales/en/component.json index e1fa53442a..f1a25ad660 100644 --- a/packages/ui/locales/en/component.json +++ b/packages/ui/locales/en/component.json @@ -99,6 +99,9 @@ }, "filter": { "delete": "Delete Filter", + "search": { + "placeholder": "Search" + }, "inputPlaceholder": "Filter by...", "buttonLabel": "Reset filters", "defaultLabel": "Filter", @@ -181,10 +184,6 @@ } }, "cancel": "Cancel", - "pagination": { - "previous": "Prev", - "next": "Next" - }, "searchFile": { "noFile": "No file found." }, @@ -237,6 +236,10 @@ "notAllowed": "Cannot create tag", "bypassButton": "Bypass rules and create tag" }, + "pagination": { + "previous": "Prev", + "next": "Next" + }, "layout": { "table": "Table", "list": "List" diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 45f9103f56..80aebccb6b 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -51,6 +51,21 @@ "name": "Name", "lastCommit": "Last commit message", "date": "Date", + "targetRepositories": "Target Repositories", + "repositories": "Repositories", + "selectIncluded": "Select included", + "selectExcluded": "Select excluded", + "selectReposTitle": "{{type}} repositories", + "selectReposDescription": "Choose repositories to {{action}} from this rule", + "repository": "Repository", + "noRepositoriesAvailable": "No repositories available", + "saveSelection": "Save Selection", + "noReposSelectedAppliesAll": "If no repositories are selected, the rule applies to all", + "patterns": "Patterns", + "targetReposCaption": "Match repositories using globstar patterns (e.g.\"repo-*\", \"prod/**\")", + "targetReposPlaceholder": "Enter the target repository patterns", + "include": "Include", + "exclude": "Exclude", "cloneCredential": "Generate Clone Credential", "expiration": "Expiration", "token": "Token", @@ -125,8 +140,6 @@ "targetPatterns": "Target patterns", "createRuleCaption": "Match branches using globstar patterns (e.g.”golden”, “feature-*”, “releases/**”)", "rulePatternPlaceholder": "Enter the target patterns", - "include": "Include", - "exclude": "Exclude", "applyRuleDefaultBranch": "Apply this rule to the default branch", "bypassList": "Bypass list", "selectUsers": "Select users", @@ -191,7 +204,6 @@ "title": "No files yet", "description": "This repository is empty. Add your first file to get started." }, - "repositories": "Repositories", "createRepository": "Create Repository", "importRepository": "Import Repository", "importRepositories": "Import Repositories", @@ -229,6 +241,10 @@ "secretScanningDescription": "Block commits containing secrets like passwords, API keys and tokens.", "vulnerabilityScanning": "Vulnerability scanning", "vulnerabilityScanningDescription": "Scan incoming commits for known vulnerabilities.", + "detectVulnerabilities": "Detect", + "detectVulnerabilitiesDescription": "Passive vulnerability will report errors but not block", + "blockVulnerabilities": "Block", + "blockVulnerabilitiesDescription": "Active vulnerability blocks commit if any vulnerability is found", "verifyCommitterIdentity": "Verify committer identity", "verifyCommitterIdentityDescription": "Block commits not committed by the user pushing the changes.", "generalSettings": "General settings", @@ -392,7 +408,6 @@ "clone": "Clone", "addFile": "Add file" }, - "search": "Search", "execution": { "summary": "Summary", "logs": "Logs", @@ -443,8 +458,10 @@ "inviteMembers": "There are no members in this project. Click on the button below to start adding them.", "noRules": "No rules yet", "noRulesDescription": "There are no rules in this project. Click on the button below to start adding rules.", - "noCommitsYet": "No commits yet", - "noCommitsYetDescription": "Your commits will appear here once they're made. Start committing to see your changes reflected.", + "pullRequests": { + "noNewCommits": "There isn’t anything to compare", + "noNewCommitsDescription": "{{source}} and {{target}} are identical." + }, "compareChanges": "Compare and review just about anything", "compareChangesDescription": "Branches and commit ranges can be reviewed within the same repository.", "title": { @@ -464,7 +481,9 @@ "createBranchDescription": "Your branches will appear here once they're created.", "startBranchDescription": "Start branching to see your work organized.", "noCommitsHistory": "No commits history", + "noCommitsYet": "No commits yet", "noCommitsHistoryDescription": "There isn't any commit history to show here for the selected user, time range, or current page.", + "noCommitsYetDescription": "Your commits will appear here once they're made. Start committing to see your changes reflected.", "clearFilters": "Clear Filters", "errorApiTitle": "Failed to load {{type}}", "errorApiDescription": "An error occurred while loading the data. Please try again and reload the page.", @@ -561,6 +580,7 @@ "compareChangesDraftTitle": "Create Draft Pull request", "compareChangesDraftDescription": "Does not request code reviews and cannot be merged.", "compareChangesCreatedButton": "Pull Request Created", + "expandSidebar": "Expand Sidebar", "compareChangesFormTitleLabel": "Add a title", "compareChangesFormTitlePlaceholder": "Enter pull request title", "compareChangesFormDescriptionHeading": "Add a description", @@ -579,7 +599,6 @@ "compareChangesCantMergeDescription": "You can still create the pull request.", "compareChangesDiscussChanges": "Discuss and review the changes in this comparison with others.", "compareChangesDiscussChangesLink": "Learn about pull requests.", - "compareChangesChooseDifferent": "Choose different branches above to discuss and review changes.", "compareChangesViewPRLink": "View pull request", "compareChangesTabOverview": "Overview", "compareChangesTabCommits": "Commits", @@ -596,6 +615,7 @@ "noLabelsSidebar": "No labels", "changedSinceLastView": "Changed since last viewed", "markViewed": "Viewed", + "collapseSidebar": "Collapse Sidebar", "deletedComment": "This comment was deleted.", "reviewers": "Reviewers", "noUsers": "No users found.", @@ -611,6 +631,7 @@ "suggestionApplied": "Suggestion applied", "codeSuggestion": "Code suggestion" }, + "compareChangesChooseDifferent": "Choose different branches above to discuss and review changes.", "deleted": "Deleted", "searchUsers": "Search users", "compareChangesFormDescriptionLabel": "Description", @@ -634,10 +655,14 @@ }, "secretDetails": { "backToSecrets": "Back to secrets", + "created": "Created", + "updated": "Updated", + "lastUsed": "Last used", "configuration": "Configuration", "references": "References", "activity": "Activity", - "configurationView": "Secret Configuration" + "configurationView": "Secret Configuration", + "lastUpdated": "Last updated" }, "notFound": { "title": "Something went wrong…", @@ -793,6 +818,81 @@ "commitDetailsDiffWith": "with", "commitDetailsDiffAdditionsAnd": "additions and", "commitDetailsDiffDeletions": "deletions" + }, + "pullRequest": { + "codeOwnersSection": { + "requestedChanges": "Code owners requested changes to the pull request", + "waitingOnReviews": "Waiting on code owner reviews of latest changes", + "changesPendingApproval": "Changes are pending approval from code owners", + "someChangesApproved": "Some changes were approved by code owners", + "latestChangesApproved": "Latest changes were approved by code owners", + "changesApproved": "Changes were approved by code owners", + "latestChangesPendingApproval": "Latest changes are pending approval from required reviewers", + "noReviewsRequired": "No codeowner reviews required", + "code": "Code", + "owners": "Owners", + "changesRequestedBy": "Changes requested by", + "approvedBy": "Approved by" + }, + "requiredMessage": "Required", + "defaultReviewersSection": { + "waitingOnReviews": "Waiting on default reviewer's reviews of latest changes", + "changesPendingApproval": "Changes are pending approval from default reviewers", + "latestChangesApproved": "Latest changes were approved by default reviewers", + "changesApproved": "Changes were approved by default reviewers", + "defaultReviewersAdded": "Default reviewers were added to the PR", + "defaultReviewers": "Default reviewers", + "changesRequestedBy": "Changes requested by", + "approvedBy": "Approved by" + }, + "changesSection": { + "approvalsMessage": "Changes were approved by {{approvalsCount}} {{approvedBy}}", + "latestApprovalsMessage": "Latest changes were approved by {{latestApprovalsCount}} {{approvedBy}}", + "changeRequestedMessage": "{{reviewer}} requested changes to the pull request", + "pendingApprovalsMessage": "{{approvalsCount}}/{{minApproval}} approvals completed", + "pendingLatestApprovalsMessage": "{{latestApprovalsCount}} {{approval}} pending on latest changes" + }, + "checksSection": { + "succeededMessage": "Succeeded in {{time}}", + "failedMessage": "Failed in {{time}}", + "runningMessage": "Running...", + "pendingMessage": "Pending...", + "erroredMessage": "Errored in {{time}}", + "detailsLink": "Details" + }, + "commentSection": { + "view": "View" + }, + "mergeSection": { + "step": "Step {{number}}.", + "step1": "Clone the repository or update your local repository with the latest changes", + "step2": "Switch to the head branch of the pull request", + "step3": "Merge the base branch into the head branch", + "step4": "Fix the conflicts and commit the results", + "comment": { + "1": "See ", + "2": "Resolving a merge conflict using the command line", + "3": "for step-by-step instructions on resolving merge conflicts" + }, + "step5": "Push the changes", + "mergeCheck": "Merge check in progress...", + "conflictsFound": "Conflicts found in this branch", + "noConflicts": "This branch has no conflicts with {{branch}} branch", + "automaticMergeCheck": "Checking for ability to merge automatically...", + "resolveConflicts": { + "1": "Use the ", + "2": "command line", + "3": " to resolve conflicts" + }, + "resolveConflictsCommandLine": "Resolve conflicts via command line", + "conflictingFiles": "Conflicting files ", + "mergeLatestChangesTitle": "This branch is out-of-date with the base branch", + "mergeLatestChanges": { + "1": "Merge the latest changes from", + "2": "into" + }, + "updateWithRebase": "Update with rebase" + } } }, "commits": { @@ -811,6 +911,7 @@ "connectors": { "filterOptions": { "statusOption": { + "pinned": "Pinned", "favorite": "Favorites", "success": "Success", "failure": "Failure", @@ -869,6 +970,25 @@ "delete": "Delete Webhook", "create": "New Webhook" }, + "search": { + "emptyState": { + "title": "Start searching", + "description": "Enter search terms to find relevant results," + }, + "showLess": "- Show Less", + "showMore": "+ Show More", + "noResultsFound": "No search results found", + "startSearching": "Start searching", + "tryDifferentQuery": "Try a different search query or clear filters", + "enterSearchTerms": "Enter search terms to find relevant results", + "searchPlaceholder": "Search anything...", + "scopePlaceholder": "Select a scope", + "repositoryPlaceholder": "Select a repository", + "statsText": { + "matchesAndFiles": "{{matchCount}} matches found in {{fileCount}} file(s)", + "files": "{{fileCount}} file(s)" + } + }, "secretActivity": { "event": "Event", "entity": "Entity", @@ -876,13 +996,10 @@ "timestamp": "Timestamp", "type": "Type" }, - "entityReference": { - "entity": "Entity", - "type": "Type", - "scope": "Scope", - "created": "Created" - }, "secrets": { + "search": { + "placeholder": "Search" + }, "filterOptions": { "secretType": { "label": "Secret Type" @@ -894,28 +1011,38 @@ "descriptionOption": { "label": "Description" }, + "tagsOption": { + "label": "Tags" + }, "typeOption": { "label": "Type" } }, "secretsTitle": "Secrets", "createNew": "Create Secret", - "edit": "Edit Secret", - "delete": "Delete Secret", + "edit": "Edit secret", + "delete": "Delete secret", "newSecret": "New Secret" }, + "entityReference": { + "entity": "Entity", + "type": "Type", + "scope": "Scope", + "created": "Created" + }, "secret": { "title": "Secret" }, "common": { "manager": "Manager", + "created": "Created", + "updated": "Updated", "lastActivity": "Last Activity", "details": "Details", "overview": "Overview", "type": "Type", "createdOn": "Created on", - "modifiedOn": "Modified on", - "created": "Created" + "modifiedOn": "Modified on" }, "userManagement": { "createUser": { diff --git a/packages/ui/locales/fr/component.json b/packages/ui/locales/fr/component.json index 9c7a76a46f..b0869eb791 100644 --- a/packages/ui/locales/fr/component.json +++ b/packages/ui/locales/fr/component.json @@ -98,6 +98,9 @@ }, "filter": { "delete": "Delete filter", + "search": { + "placeholder": "Search" + }, "inputPlaceholder": "Filtrer par...", "buttonLabel": "Réinitialiser les filtres", "defaultLabel": "Filtrer", @@ -180,10 +183,6 @@ } }, "cancel": "Cancel", - "pagination": { - "previous": "Précédent", - "next": "Suivant" - }, "searchFile": { "noFile": "Aucun fichier trouvé." }, @@ -237,6 +236,10 @@ "notAllowed": "Cannot create tag", "bypassButton": "Bypass rules and create tag" }, + "pagination": { + "previous": "Précédent", + "next": "Suivant" + }, "layout": { "table": "Table", "list": "List" diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index c63c870dcd..a2e8d95197 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -51,6 +51,21 @@ "name": "Nom", "lastCommit": "Dernier commit", "date": "Date", + "targetRepositories": "Target Repositories", + "repositories": "Dépôts", + "selectIncluded": "Select included", + "selectExcluded": "Select excluded", + "selectReposTitle": "Dépôts {{type}}", + "selectReposDescription": "Choisissez les dépôts à {{action}} de cette règle", + "repository": "Dépôt", + "noRepositoriesAvailable": "No repositories available", + "saveSelection": "Save Selection", + "noReposSelectedAppliesAll": "If no repositories are selected, the rule applies to all", + "patterns": "Patterns", + "targetReposCaption": "Match repositories using globstar patterns (e.g.\"repo-*\", \"prod/**\")", + "targetReposPlaceholder": "Enter the target repository patterns", + "include": "Include", + "exclude": "Exclude", "cloneCredential": "Générer un identifiant de clonage", "expiration": "Expiration", "token": "Jeton", @@ -125,8 +140,6 @@ "targetPatterns": "Modèles cibles", "createRuleCaption": "Définir les branches correspondant aux modèles globaux (ex : 'feature-*', 'release/**')", "rulePatternPlaceholder": "Enter the target patterns", - "include": "Include", - "exclude": "Exclude", "applyRuleDefaultBranch": "Appliquer la règle à la branche par défaut", "bypassList": "Liste de contournement", "selectUsers": "Sélectionner les utilisateurs", @@ -191,7 +204,6 @@ "title": "No files yet", "description": "This repository is empty. Add your first file to get started." }, - "repositories": "Dépôts", "createRepository": "Créer un dépôt", "importRepository": "Importer un dépôt", "importRepositories": "Import repositories", @@ -228,9 +240,13 @@ "secretScanning": "Analyse des secrets", "secretScanningDescription": "Empêcher les commits contenant des secrets comme les mots de passe ou jetons API.", "vulnerabilityScanning": "Vulnerability scanning", - "vulnerabilityScanningDescription": "Scan incoming commits for known vulnerabilities.", + "vulnerabilityScanningDescription": "Scanner les commits entrants pour les vulnérabilités connues.", + "detectVulnerabilities": "Detect", + "detectVulnerabilitiesDescription": "Vulnérabilité passive signalera des erreurs mais ne bloquera pas", + "blockVulnerabilities": "Block", + "blockVulnerabilitiesDescription": "Une vulnérabilité active bloquera le commit si une vulnérabilité est trouvée.", "verifyCommitterIdentity": "Verify committer identity", - "verifyCommitterIdentityDescription": "Block commits not committed by the user pushing the changes.", + "verifyCommitterIdentityDescription": "Bloquer les commits non-validés par l'utilisateur qui effectue les modifications.", "generalSettings": "Paramètres généraux", "general": "Général", "labels": "Labels", @@ -383,7 +399,6 @@ "clone": "Cloner", "addFile": "Ajouter un fichier" }, - "search": "Rechercher", "execution": { "summary": "Résumé", "logs": "Journaux", @@ -434,8 +449,10 @@ "inviteMembers": "", "noRules": "No rules yet", "noRulesDescription": "There are no rules in this project. Click on the button below to start adding rules.", - "noCommitsYet": "No commits yet", - "noCommitsYetDescription": "Your commits will appear here once they're made. Start committing to see your changes reflected.", + "pullRequests": { + "noNewCommits": "There isn’t anything to compare", + "noNewCommitsDescription": "{{source}} and {{target}} are identical." + }, "compareChanges": "Comparez et révisez pratiquement tout", "compareChangesDescription": "Branches, tags, plages de commits et périodes. Dans le même dépôt et entre les forks.", "title": { @@ -455,7 +472,9 @@ "createBranchDescription": "Vos branches apparaîtront ici une fois créées.", "startBranchDescription": "Commencez à créer des branches pour voir votre travail organisé.", "noCommitsHistory": "No commits history", + "noCommitsYet": "No commits yet", "noCommitsHistoryDescription": "There isn't any commit history to show here for the selected user, time range, or current page.", + "noCommitsYetDescription": "Your commits will appear here once they're made. Start committing to see your changes reflected.", "clearFilters": "Effacer les filtres", "errorApiTitle": "Échec du chargement de {{type}}", "errorApiDescription": "Une erreur s'est produite lors du chargement des données. Veuillez réessayer et recharger la page.", @@ -552,6 +571,7 @@ "compareChangesDraftTitle": "Créer une pull request en brouillon", "compareChangesDraftDescription": "Ne demande pas de révision de code et ne peut pas être fusionnée.", "compareChangesCreatedButton": "Pull request créée", + "expandSidebar": "Expand Sidebar", "compareChangesFormTitleLabel": "Add a title", "compareChangesFormTitlePlaceholder": "Saisissez le titre de la pull request", "compareChangesFormDescriptionHeading": "Add a description", @@ -570,7 +590,6 @@ "compareChangesCantMergeDescription": "Vous pouvez quand même créer la pull request.", "compareChangesDiscussChanges": "Discutez et révisez les modifications de cette comparaison avec d'autres.", "compareChangesDiscussChangesLink": "En savoir plus sur les pull requests.", - "compareChangesChooseDifferent": "Choisissez différentes branches ou forks ci-dessus pour discuter et réviser les modifications.", "compareChangesViewPRLink": "Voir la pull request", "compareChangesTabOverview": "Aperçu", "compareChangesTabCommits": "Commits", @@ -587,6 +606,7 @@ "noLabelsSidebar": "No labels", "changedSinceLastView": "Modifié depuis la dernière consultation", "markViewed": "Vue", + "collapseSidebar": "Collapse Sidebar", "deletedComment": "Ce commentaire a été supprimé.", "reviewers": "Réviseurs", "noUsers": "Aucun utilisateur trouvé.", @@ -602,6 +622,7 @@ "suggestionApplied": "Suggestion applied", "codeSuggestion": "Code suggestion" }, + "compareChangesChooseDifferent": "Choisissez différentes branches ou forks ci-dessus pour discuter et réviser les modifications.", "deleted": "Supprimé", "searchUsers": "Rechercher des utilisateurs", "compareChangesFormDescriptionLabel": "Description", @@ -621,10 +642,14 @@ }, "secretDetails": { "backToSecrets": "Back to secrets", + "created": "Created", + "updated": "Updated", + "lastUsed": "Last used", "configuration": "Configuration", "references": "References", "activity": "Activity", - "configurationView": "Secret Configuration" + "configurationView": "Secret Configuration", + "lastUpdated": "Last updated" }, "notFound": { "title": "Quelque chose s'est mal passé…", @@ -779,6 +804,81 @@ "commitDetailsDiffWith": "avec", "commitDetailsDiffAdditionsAnd": "ajouts et", "commitDetailsDiffDeletions": "suppressions" + }, + "pullRequest": { + "codeOwnersSection": { + "requestedChanges": "Code owners requested changes to the pull request", + "waitingOnReviews": "Waiting on code owner reviews of latest changes", + "changesPendingApproval": "Changes are pending approval from code owners", + "someChangesApproved": "Some changes were approved by code owners", + "latestChangesApproved": "Latest changes were approved by code owners", + "changesApproved": "Changes were approved by code owners", + "latestChangesPendingApproval": "Latest changes are pending approval from required reviewers", + "noReviewsRequired": "No codeowner reviews required", + "code": "Code", + "owners": "Owners", + "changesRequestedBy": "Changes requested by", + "approvedBy": "Approved by" + }, + "requiredMessage": "Required", + "defaultReviewersSection": { + "waitingOnReviews": "Waiting on default reviewer's reviews of latest changes", + "changesPendingApproval": "Changes are pending approval from default reviewers", + "latestChangesApproved": "Latest changes were approved by default reviewers", + "changesApproved": "Changes were approved by default reviewers", + "defaultReviewersAdded": "Default reviewers were added to the PR", + "defaultReviewers": "Default reviewers", + "changesRequestedBy": "Changes requested by", + "approvedBy": "Approved by" + }, + "changesSection": { + "approvalsMessage": "Changes were approved by {{approvalsCount}} {{approvedBy}}", + "latestApprovalsMessage": "Latest changes were approved by {{latestApprovalsCount}} {{approvedBy}}", + "changeRequestedMessage": "{{reviewer}} requested changes to the pull request", + "pendingApprovalsMessage": "{{approvalsCount}}/{{minApproval}} approvals completed", + "pendingLatestApprovalsMessage": "{{latestApprovalsCount}} {{approval}} pending on latest changes" + }, + "checksSection": { + "succeededMessage": "Succeeded in {{time}}", + "failedMessage": "Failed in {{time}}", + "runningMessage": "Running...", + "pendingMessage": "Pending...", + "erroredMessage": "Errored in {{time}}", + "detailsLink": "Details" + }, + "commentSection": { + "view": "View" + }, + "mergeSection": { + "step": "Step {{number}}.", + "step1": "Clone the repository or update your local repository with the latest changes", + "step2": "Switch to the head branch of the pull request", + "step3": "Merge the base branch into the head branch", + "step4": "Fix the conflicts and commit the results", + "comment": { + "1": "See ", + "2": "Resolving a merge conflict using the command line", + "3": "for step-by-step instructions on resolving merge conflicts" + }, + "step5": "Push the changes", + "mergeCheck": "Merge check in progress...", + "conflictsFound": "Conflicts found in this branch", + "noConflicts": "This branch has no conflicts with {{branch}} branch", + "automaticMergeCheck": "Checking for ability to merge automatically...", + "resolveConflicts": { + "1": "Use the ", + "2": "command line", + "3": " to resolve conflicts" + }, + "resolveConflictsCommandLine": "Resolve conflicts via command line", + "conflictingFiles": "Conflicting files ", + "mergeLatestChangesTitle": "This branch is out-of-date with the base branch", + "mergeLatestChanges": { + "1": "Merge the latest changes from", + "2": "into" + }, + "updateWithRebase": "Update with rebase" + } } }, "commits": { @@ -797,6 +897,7 @@ "connectors": { "filterOptions": { "statusOption": { + "pinned": "Pinned", "favorite": "Favorites", "success": "Succès", "failure": "Échec", @@ -855,6 +956,25 @@ "delete": "Delete webhook", "create": "Create webhook" }, + "search": { + "emptyState": { + "title": "Start searching", + "description": "Enter search terms to find relevant results," + }, + "showLess": "- Show Less", + "showMore": "+ Show More", + "noResultsFound": "No search results found", + "startSearching": "Start searching", + "tryDifferentQuery": "Try a different search query or clear filters", + "enterSearchTerms": "Enter search terms to find relevant results", + "searchPlaceholder": "Search anything...", + "scopePlaceholder": "Select a scope", + "repositoryPlaceholder": "Select a repository", + "statsText": { + "matchesAndFiles": "{{matchCount}} matches found in {{fileCount}} file(s)", + "files": "{{fileCount}} file(s)" + } + }, "secretActivity": { "event": "Event", "entity": "Entity", @@ -862,13 +982,10 @@ "timestamp": "Timestamp", "type": "Type" }, - "entityReference": { - "entity": "Entity", - "type": "Type", - "scope": "Scope", - "created": "Created" - }, "secrets": { + "search": { + "placeholder": "Search" + }, "filterOptions": { "secretType": { "label": "Secret Type" @@ -880,28 +997,38 @@ "descriptionOption": { "label": "Description" }, + "tagsOption": { + "label": "Tags" + }, "typeOption": { "label": "Type" } }, "secretsTitle": "Secrets", "createNew": "Créer un secret", - "edit": "Edit Secret", + "edit": "Edit secret", "delete": "Supprimer le secret", "newSecret": "New Secret" }, + "entityReference": { + "entity": "Entity", + "type": "Type", + "scope": "Scope", + "created": "Created" + }, "secret": { "title": "Secret" }, "common": { "manager": "Manager", + "created": "Created", + "updated": "Updated", "lastActivity": "Last Activity", "details": "Details", "overview": "Overview", "type": "Type", "createdOn": "Created on", - "modifiedOn": "Modified on", - "created": "Created" + "modifiedOn": "Modified on" }, "userManagement": { "createUser": { diff --git a/packages/ui/package.json b/packages/ui/package.json index fae3eba0b1..abd6f85d42 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,7 +1,7 @@ { "name": "@harnessio/ui", "description": "Harness Canary UI component library", - "version": "0.0.134", + "version": "0.0.156", "private": false, "type": "module", "main": "./dist/index.js", @@ -77,10 +77,8 @@ "@radix-ui/react-context-menu": "^2.2.4", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.4", - "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-menubar": "^1.1.4", - "@radix-ui/react-navigation-menu": "^1.2.3", "@radix-ui/react-popover": "^1.1.4", "@radix-ui/react-progress": "^1.1.1", "@radix-ui/react-radio-group": "^1.2.2", @@ -126,8 +124,7 @@ "rehype-sanitize": "^6.0.0", "rehype-video": "^2.0.2", "remark-breaks": "^4.0.0", - "simple-icons": "^14.11.1", - "sonner": "^1.5.0", + "sonner": "^1.7.4", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "vaul": "^1.1.2", @@ -146,7 +143,6 @@ "@types/lodash-es": "^4.17.12", "@types/node": "^22.9.0", "@types/react": "^17.0.3", - "@types/simple-icons": "^5.21.1", "@vitejs/plugin-react-swc": "^3.8.0", "@vitest/coverage-istanbul": "^3.0.6", "@vitest/ui": "^3.0.6", diff --git a/packages/ui/src/components/app-sidebar/sidebar-item.tsx b/packages/ui/src/components/app-sidebar/sidebar-item.tsx index 494e0807a9..db55b0483c 100644 --- a/packages/ui/src/components/app-sidebar/sidebar-item.tsx +++ b/packages/ui/src/components/app-sidebar/sidebar-item.tsx @@ -1,4 +1,4 @@ -import { NavbarItemType, Sidebar } from '@/components' +import { NavbarItemType, Sidebar, SidebarItemProps } from '@/components' import { useTranslation } from '@/context' import { wrapConditionalArrayElements } from '@utils/mergeUtils' @@ -9,6 +9,8 @@ interface NavbarItemProps { handleRemoveRecentMenuItem: (item: NavbarItemType) => void handleCustomNav: () => void disabled?: boolean + actionButtons?: SidebarItemProps['actionButtons'] + hideMenuItems?: boolean } export const AppSidebarItem = ({ @@ -17,7 +19,9 @@ export const AppSidebarItem = ({ handleChangePinnedMenuItem, handleRemoveRecentMenuItem, handleCustomNav, - disabled = false + disabled = false, + actionButtons, + hideMenuItems = false }: NavbarItemProps) => { const { t } = useTranslation() @@ -39,12 +43,14 @@ export const AppSidebarItem = ({ return ( ) } +AppSidebarItem.displayName = 'AppSidebarItem' diff --git a/packages/ui/src/components/app-sidebar/sidebar-search/command-palette-wrapper.tsx b/packages/ui/src/components/app-sidebar/sidebar-search/command-palette-wrapper.tsx index 791904d8f3..464e5b9a6d 100644 --- a/packages/ui/src/components/app-sidebar/sidebar-search/command-palette-wrapper.tsx +++ b/packages/ui/src/components/app-sidebar/sidebar-search/command-palette-wrapper.tsx @@ -130,12 +130,12 @@ export function CommandPaletteWrapper() { { label: 'Canary', url: '/canary/repos/petstore-app/summary', - icon: () => renderIcon('page') + icon: () => renderIcon('empty-page') }, { label: 'Paypal', url: '/canary/repos/real-world/summary', - icon: () => renderIcon('page') + icon: () => renderIcon('empty-page') } ], [PageKey.PIPELINES]: [ diff --git a/packages/ui/src/components/app-sidebar/sidebar-search/sidebar-search.tsx b/packages/ui/src/components/app-sidebar/sidebar-search/sidebar-search.tsx index 3112d5dcf6..8ce9e9b9d2 100644 --- a/packages/ui/src/components/app-sidebar/sidebar-search/sidebar-search.tsx +++ b/packages/ui/src/components/app-sidebar/sidebar-search/sidebar-search.tsx @@ -21,7 +21,7 @@ export function SidebarSearch() { size="sm" placeholder={t('component:navbar.search', 'Search')} className="pointer-events-none" - inputContainerClassName="border-cn-2 [&>.cn-input-prefix]:w-[34px] max-w-full overflow-hidden" + inputContainerClassName="border-cn-2 max-w-full overflow-hidden" suffix={⌘K} readOnly aria-hidden="true" diff --git a/packages/ui/src/components/app-sidebar/sidebar-user.tsx b/packages/ui/src/components/app-sidebar/sidebar-user.tsx index e28700ee99..1c9f876444 100644 --- a/packages/ui/src/components/app-sidebar/sidebar-user.tsx +++ b/packages/ui/src/components/app-sidebar/sidebar-user.tsx @@ -25,6 +25,7 @@ export const AppSidebarUser = ({ user, openThemeDialog, openLanguageDialog, hand description={user?.email} avatarFallback={userName} src={user?.url} + className="!ml-0" dropdownMenuContent={ <> diff --git a/packages/ui/src/components/app-sidebar/types.ts b/packages/ui/src/components/app-sidebar/types.ts index 4665d223a7..31fb4f2152 100644 --- a/packages/ui/src/components/app-sidebar/types.ts +++ b/packages/ui/src/components/app-sidebar/types.ts @@ -21,6 +21,7 @@ interface NavbarItemType { description?: string to: string permanentlyPinned?: boolean + subItems?: NavbarItemType[] } export enum UserMenuKeys { diff --git a/packages/ui/src/components/avatar.tsx b/packages/ui/src/components/avatar.tsx index 8fc2e38395..ebcf80e926 100644 --- a/packages/ui/src/components/avatar.tsx +++ b/packages/ui/src/components/avatar.tsx @@ -34,24 +34,28 @@ export interface AvatarProps extends ComponentPropsWithoutRef<'span'> { src?: string size?: VariantProps['size'] rounded?: boolean + noInitials?: boolean } const Avatar = forwardRef( - ({ name, src, size = 'sm', rounded = false, className, ...props }, ref) => { - const initials = getInitials(name || '') + ({ name = '', src, size = 'sm', rounded = false, className, noInitials = false, ...props }, ref) => { + const initials = noInitials ? name : getInitials(name) return ( {src ? ( <> - + {/* TODO: Design system: Check whether we need cn-avatar-icon */} {initials || } ) : ( - + 3 ? 'cn-avatar-fallback-small' : ''}`} + delayMs={0} + > {initials || } )} diff --git a/packages/ui/src/components/button-group.tsx b/packages/ui/src/components/button-group.tsx index 610f849aa0..3599a0d1c5 100644 --- a/packages/ui/src/components/button-group.tsx +++ b/packages/ui/src/components/button-group.tsx @@ -1,6 +1,6 @@ import { ComponentProps, FC, forwardRef, ReactNode, Ref } from 'react' -import { Button, ButtonProps, DropdownMenu, toButtonProps, TooltipProps } from '@/components' +import { Button, ButtonProps, DropdownMenu, TooltipProps } from '@/components' import { cn } from '@utils/cn' import omit from 'lodash-es/omit' @@ -78,11 +78,9 @@ export const ButtonGroup = forwardRef( )} variant={variant ?? 'outline'} size={size} - {...toButtonProps({ - ...omit(restButtonProps, ['tooltipProps', 'dropdownProps']), - iconOnly, - tooltipProps: mergedTooltip - })} + {...omit(restButtonProps, ['tooltipProps', 'dropdownProps'])} + iconOnly={iconOnly} + tooltipProps={mergedTooltip} /> ) diff --git a/packages/ui/src/components/button.tsx b/packages/ui/src/components/button.tsx index 8b1558d7c1..a7d1c0d9ca 100644 --- a/packages/ui/src/components/button.tsx +++ b/packages/ui/src/components/button.tsx @@ -1,10 +1,11 @@ -import { ButtonHTMLAttributes, forwardRef, Fragment } from 'react' +import { ButtonHTMLAttributes, forwardRef, Fragment, useCallback, useState } from 'react' import { Tooltip, TooltipProps } from '@components/tooltip' import { Slot } from '@radix-ui/react-slot' import { cn } from '@utils/cn' -import { filterChildrenByDisplayNames } from '@utils/utils' +import { filterChildrenByDisplayNames, isPromise } from '@utils/utils' import { cva, type VariantProps } from 'class-variance-authority' +import isEmpty from 'lodash-es/isEmpty' import { IconV2, IconV2DisplayName } from './icon-v2' @@ -30,10 +31,6 @@ const buttonVariants = cva('cn-button', { true: 'cn-button-rounded' }, - iconOnly: { - true: 'cn-button-icon-only' - }, - theme: { default: 'cn-button-default', success: 'cn-button-success', @@ -47,18 +44,18 @@ const buttonVariants = cva('cn-button', { } }) -type ButtonTooltipProps = Pick +type ButtonTooltipProps = Pick -type CommonButtonProps = ButtonHTMLAttributes & +type CommonButtonProps = Omit, 'onClick'> & VariantProps & { asChild?: boolean loading?: boolean rounded?: boolean + onClick?: (event: React.MouseEvent) => void | Promise } type ButtonPropsIconOnlyRequired = { iconOnly: true - ignoreIconOnlyTooltip?: false tooltipProps: ButtonTooltipProps } @@ -69,9 +66,8 @@ type ButtonPropsIconOnlyIgnored = { } type ButtonPropsRegular = { - iconOnly?: false + iconOnly?: boolean tooltipProps?: ButtonTooltipProps - ignoreIconOnlyTooltip?: boolean } type ButtonProps = CommonButtonProps & (ButtonPropsIconOnlyRequired | ButtonPropsIconOnlyIgnored | ButtonPropsRegular) @@ -96,19 +92,41 @@ const Button = forwardRef( disabled, children: _children, type = 'button', - tooltipProps, - ignoreIconOnlyTooltip, + onClick, ...props }, ref ) => { + const [isPromiseLoading, setIsPromiseLoading] = useState(false) + const Comp = asChild ? Slot : 'button' const microSize = size === '2xs' || size === '3xs' const iconOnly = iconOnlyProp || microSize + // Handle onClick that might return a promise + const handleClick = useCallback( + (event: React.MouseEvent) => { + if (onClick) { + const result = onClick(event) + + // Check if onClick returned a promise (result could be void) + if (isPromise(result)) { + setIsPromiseLoading(true) + ;(result as Promise).finally(() => { + setIsPromiseLoading(false) + }) + } + } + }, + [onClick] + ) + const filteredChildren = iconOnly ? filterChildrenByDisplayNames(_children, [IconV2DisplayName])[0] : _children - const children = loading ? ( + // Show loading if either loading prop is true or promise is loading + const isLoading = loading || isPromiseLoading + + const children = isLoading ? ( <> {/* When button state is 'loading' and iconOnly is true, we show only 1 icon */} @@ -120,19 +138,24 @@ const Button = forwardRef( const ButtonComp = ( {children} ) - if (tooltipProps && !ignoreIconOnlyTooltip) { + if (!isEmpty(props.tooltipProps)) { + const buttonTooltipProps = props.tooltipProps + return ( - + {ButtonComp} ) @@ -143,34 +166,7 @@ const Button = forwardRef( ) Button.displayName = 'Button' -/** - * Converts iconOnly into a literal and returns props ready to be spread into ) @@ -258,7 +257,7 @@ const CarouselNext = forwardRef( }} {...props} > - + Next slide ) diff --git a/packages/ui/src/components/command.tsx b/packages/ui/src/components/command.tsx index 1e211d08bb..3930918a62 100644 --- a/packages/ui/src/components/command.tsx +++ b/packages/ui/src/components/command.tsx @@ -1,8 +1,7 @@ import * as React from 'react' -import { Dialog, ScrollArea, ScrollAreaProps } from '@/components' +import { Dialog, IconV2, ScrollArea, ScrollAreaProps } from '@/components' import { type DialogProps } from '@radix-ui/react-dialog' -import { MagnifyingGlassIcon } from '@radix-ui/react-icons' import { cn } from '@utils/cn' import { Command as CommandPrimitive } from 'cmdk' @@ -36,7 +35,7 @@ const CommandInput = React.forwardRef< >(({ className, ...props }, ref) => ( // eslint-disable-next-line react/no-unknown-property
- + { data: TData[] columns: ColumnDef[] size?: VariantProps['size'] - pagination?: PaginationProps + paginationProps?: PaginationProps getRowClassName?: (row: Row) => string | undefined onRowClick?: (data: TData, index: number) => void disableHighlightOnHover?: boolean @@ -81,7 +81,7 @@ export function DataTable({ data = [], columns, size = 'normal', - pagination, + paginationProps, getRowClassName, onRowClick, disableHighlightOnHover = false, @@ -230,61 +230,62 @@ export function DataTable({ const table = useReactTable(tableOptions) return ( -
- - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} - {_enableColumnResizing && header.column.getCanResize() && ( -
+ )} + + ))} + + ) } diff --git a/packages/ui/src/components/dialog.tsx b/packages/ui/src/components/dialog.tsx index d37fb86581..5596026801 100644 --- a/packages/ui/src/components/dialog.tsx +++ b/packages/ui/src/components/dialog.tsx @@ -1,21 +1,7 @@ -import { - Children, - createContext, - forwardRef, - HTMLAttributes, - isValidElement, - MouseEvent, - ReactNode, - useCallback, - useContext, - useEffect, - useRef, - useState -} from 'react' +import { Children, forwardRef, HTMLAttributes, isValidElement, ReactNode, useCallback } from 'react' -import { usePortal, useTranslation } from '@/context' +import { DialogOpenContext, TriggerBase, usePortal, useRegisterDialog, useTranslation } from '@/context' import * as DialogPrimitive from '@radix-ui/react-dialog' -import { Slot } from '@radix-ui/react-slot' import { cn } from '@utils/cn' import { cva, type VariantProps } from 'class-variance-authority' @@ -51,111 +37,8 @@ const headerVariants = cva('cn-modal-dialog-header', { } }) -interface FocusEntry { - triggerId: string - triggerElement: HTMLElement -} - -interface DialogFocusContextValue { - registerTrigger: (entry: FocusEntry) => void - unregisterTrigger: (id: string) => void - restoreFocus: (id: string) => void - getLastTrigger: () => FocusEntry | undefined -} - -const DialogContext = createContext(undefined) - -const useDialogFocusManager = () => { - const context = useContext(DialogContext) - - return context -} - -/** - * DialogProvider component manages focus for dialog triggers. - * It ensures that the last focused element is restored when the dialog is closed. - */ -const DialogProvider = ({ children }: { children: ReactNode }) => { - const focusStack = useRef([]) - - const unregisterTrigger = useCallback((id: string) => { - focusStack.current = focusStack.current.filter(e => e.triggerId !== id) - }, []) - - const registerTrigger = useCallback((entry: FocusEntry) => { - unregisterTrigger(entry.triggerId) - focusStack.current.push(entry) - }, []) - - const restoreFocus = useCallback((id: string) => { - const entryIndex = focusStack.current.findIndex(e => e.triggerId === id) - - if (entryIndex !== -1) { - const entry = focusStack.current[entryIndex] - if (entry.triggerElement) { - setTimeout(() => entry.triggerElement.focus(), 0) - } - unregisterTrigger(entry.triggerId) - } else { - const lastEntry = focusStack.current.at(-1) - if (lastEntry?.triggerElement) { - setTimeout(() => lastEntry.triggerElement.focus(), 0) - } - } - }, []) - - const getLastTrigger = useCallback(() => { - return focusStack.current.at(-1) - }, []) - - return ( - - {children} - - ) -} - -let triggerCounter = 0 - -const useTriggerId = (_id?: string) => { - const id = useRef(`${_id || 'dialog-trigger'}-${triggerCounter++}`) - return id.current -} - -/** - * Custom hook to manage dialog trigger elements. - * It provides a ref for the trigger element and a callback to register the trigger. - * Useful when a dialog is opened from a complex components like dropdowns, etc. - */ -const useCustomDialogTrigger = () => { - const focusManager = useDialogFocusManager() - const triggerId = useTriggerId() - const triggerRef = useRef(null) - - const registerTrigger = useCallback(() => { - if (focusManager && triggerRef.current) { - focusManager.registerTrigger({ triggerId, triggerElement: triggerRef.current }) - } - }, [focusManager, triggerId]) - - return { triggerRef, registerTrigger } -} - export type ModalDialogRootProps = Pick -interface DialogOpenContextValue { - open?: boolean -} - -const DialogOpenContext = createContext(undefined) -const useDialogOpen = () => { - const context = useContext(DialogOpenContext) - if (!context) { - throw new Error('useDialogOpen must be used within a DialogOpenProvider') - } - return context -} - const Root = ({ children, open, ...props }: ModalDialogRootProps) => { return ( @@ -172,33 +55,17 @@ interface ContentProps extends DialogPrimitive.DialogContentProps, VariantProps< } const Content = forwardRef( - ({ children, className, size = 'sm', hideClose = false, ...props }, ref) => { + ({ children, className, size = 'sm', hideClose = false, onCloseAutoFocus: _onCloseAutoFocus, ...props }, ref) => { const { portalContainer } = usePortal() - const focusManager = useDialogFocusManager() - const [triggerId, setTriggerId] = useState('') - const { open } = useDialogOpen() - - const handleCloseAutoFocus = useCallback(() => { - if (focusManager) { - focusManager.restoreFocus(triggerId) - } - }, [focusManager, triggerId]) - - useEffect(() => { - if (focusManager && open) { - const triggerId = focusManager.getLastTrigger()?.triggerId - if (!triggerId) return - setTriggerId(triggerId) - } - }, [open]) - - useEffect(() => { - return () => { - if (focusManager) { - focusManager.unregisterTrigger(triggerId) - } - } - }, []) + const { handleCloseAutoFocus } = useRegisterDialog() + + const onCloseAutoFocus = useCallback( + (e: Event) => { + handleCloseAutoFocus() + _onCloseAutoFocus?.(e) + }, + [_onCloseAutoFocus, handleCloseAutoFocus] + ) return ( @@ -209,8 +76,8 @@ const Content = forwardRef( {!hideClose && ( @@ -328,47 +195,16 @@ const Close = forwardRef(({ children, className, )) Close.displayName = 'Dialog.Close' -/** - * Dialog trigger component is essential for opening dialogs. - * It registers the trigger element with the dialog focus manager. - */ -const Trigger = forwardRef & { children: ReactNode }>( - ({ onClick, id, children, ...props }, ref) => { - const triggerId = useTriggerId(id) - const focusManager = useDialogFocusManager() - - const dialogContext = useContext(DialogOpenContext) - const isInsideDialog = dialogContext !== undefined - const childrenCount = Children.count(children) - - if (childrenCount > 1) { - console.warn('Dialog.Trigger: Only one child is allowed') - children = {children} - } - - const handleClick = (event: MouseEvent) => { - if (focusManager) { - focusManager.registerTrigger({ triggerId, triggerElement: event.currentTarget }) - } - onClick?.(event) - } - - const trigger = ( - - {children} - - ) - - if (isInsideDialog && !onClick) { - return {trigger} - } - - return trigger - } -) +const Trigger = forwardRef>(({ children, ...props }, ref) => { + return ( + + {children} + + ) +}) Trigger.displayName = 'DialogTrigger' -const Dialog = { +export const Dialog = { Root, Trigger, Content, @@ -379,5 +215,3 @@ const Dialog = { Body, Footer } - -export { Dialog, DialogProvider, useCustomDialogTrigger } diff --git a/packages/ui/src/components/drawer/DrawerContent.tsx b/packages/ui/src/components/drawer/DrawerContent.tsx index 2091755306..fc628ff513 100644 --- a/packages/ui/src/components/drawer/DrawerContent.tsx +++ b/packages/ui/src/components/drawer/DrawerContent.tsx @@ -1,7 +1,7 @@ -import { ComponentPropsWithoutRef, ElementRef, forwardRef, useRef } from 'react' +import { ComponentPropsWithoutRef, ElementRef, forwardRef, useCallback, useRef } from 'react' import { Button, IconV2 } from '@/components' -import { usePortal } from '@/context' +import { usePortal, useRegisterDialog } from '@/context' import { cn, useMergeRefs } from '@/utils' import { cva, VariantProps } from 'class-variance-authority' import { Drawer as DrawerPrimitive } from 'vaul' @@ -12,6 +12,7 @@ import { DrawerOverlay } from './DrawerOverlay' const drawerContentVariants = cva('cn-drawer-content', { variants: { size: { + '2xs': 'cn-drawer-content-2xs', xs: 'cn-drawer-content-xs', sm: 'cn-drawer-content-sm', md: 'cn-drawer-content-md', @@ -50,6 +51,7 @@ export const DrawerContent = forwardRef { + handleCloseAutoFocus() + _onCloseAutoFocus?.(e) + }, + [_onCloseAutoFocus, handleCloseAutoFocus] + ) + const withCustomOverlay = forceWithOverlay && modal === false const Content = ( @@ -69,6 +81,7 @@ export const DrawerContent = forwardRef { e.preventDefault() onOpenAutoFocus?.(e) diff --git a/packages/ui/src/components/drawer/DrawerRoot.tsx b/packages/ui/src/components/drawer/DrawerRoot.tsx index 449f36f2fb..d3cff29bf8 100644 --- a/packages/ui/src/components/drawer/DrawerRoot.tsx +++ b/packages/ui/src/components/drawer/DrawerRoot.tsx @@ -1,6 +1,6 @@ import { ComponentProps, useEffect, useRef, useState } from 'react' -import { usePortal } from '@/context' +import { DialogOpenContext, usePortal } from '@/context' import { Drawer as DrawerPrimitive } from 'vaul' import styleText from 'vaul/style.css?raw' @@ -71,10 +71,12 @@ export const DrawerRoot = ({ - - {nested && FakeTriggers} - {children} - + + + {nested && FakeTriggers} + {children} + + ) } diff --git a/packages/ui/src/components/drawer/DrawerTrigger.tsx b/packages/ui/src/components/drawer/DrawerTrigger.tsx new file mode 100644 index 0000000000..1c4be0dc83 --- /dev/null +++ b/packages/ui/src/components/drawer/DrawerTrigger.tsx @@ -0,0 +1,15 @@ +import { forwardRef, HTMLAttributes } from 'react' + +import { TriggerBase } from '@/context' +import { Drawer as DrawerPrimitive } from 'vaul' + +export const DrawerTrigger = forwardRef>( + ({ children, ...props }, ref) => { + return ( + + {children} + + ) + } +) +DrawerTrigger.displayName = 'Dialog.Trigger' diff --git a/packages/ui/src/components/drawer/index.ts b/packages/ui/src/components/drawer/index.ts index 125987fe50..f58ed1d914 100644 --- a/packages/ui/src/components/drawer/index.ts +++ b/packages/ui/src/components/drawer/index.ts @@ -13,8 +13,8 @@ import { DrawerFooter } from './DrawerFooter' import { DrawerHeader, DrawerHeaderProps } from './DrawerHeader' import { DrawerRoot } from './DrawerRoot' import { DrawerTitle } from './DrawerTitle' +import { DrawerTrigger } from './DrawerTrigger' -const DrawerTrigger = DrawerPrimitive.Trigger const DrawerClose = DrawerPrimitive.Close export type { DrawerContentVariantsSize, DrawerContentVariantsDirection, DrawerContentProps, DrawerHeaderProps } diff --git a/packages/ui/src/components/dropdown-menu.tsx b/packages/ui/src/components/dropdown-menu.tsx index c35e63109a..359115f2c2 100644 --- a/packages/ui/src/components/dropdown-menu.tsx +++ b/packages/ui/src/components/dropdown-menu.tsx @@ -459,11 +459,16 @@ DropdownMenuLogoItem.displayName = displayNames.logoItem interface DropdownMenuIconItemProps extends Omit { icon: IconPropsV2['name'] iconClassName?: string + iconSize?: IconPropsV2['size'] } const DropdownMenuIconItem = forwardRef, DropdownMenuIconItemProps>( - ({ icon, iconClassName, ...props }, ref) => ( - } /> + ({ icon, iconClassName, iconSize, ...props }, ref) => ( + } + /> ) ) DropdownMenuIconItem.displayName = displayNames.iconItem diff --git a/packages/ui/src/components/favorite.tsx b/packages/ui/src/components/favorite.tsx index befa373e57..320b8ba28b 100644 --- a/packages/ui/src/components/favorite.tsx +++ b/packages/ui/src/components/favorite.tsx @@ -1,29 +1,28 @@ import { FC } from 'react' import { Toggle } from '@/components' +import { cn } from '@/utils/cn' interface FavoriteIconProps { isFavorite?: boolean onFavoriteToggle: (isFavorite: boolean) => void + className?: string } -const Favorite: FC = ({ isFavorite = false, onFavoriteToggle }) => ( +const Favorite: FC = ({ isFavorite = false, onFavoriteToggle, className }) => ( onFavoriteToggle(selected)} tooltipProps={{ - content: isFavorite ? 'Remove from favorite' : 'Add to favorite' + content: isFavorite ? 'Unpin' : 'Pin' }} /> ) -export { Favorite } +export { Favorite, type FavoriteIconProps } diff --git a/packages/ui/src/components/file-control-bars/file-viewer-control-bar.tsx b/packages/ui/src/components/file-control-bars/file-viewer-control-bar.tsx index c3dd574f92..4cf613641f 100644 --- a/packages/ui/src/components/file-control-bars/file-viewer-control-bar.tsx +++ b/packages/ui/src/components/file-control-bars/file-viewer-control-bar.tsx @@ -10,9 +10,9 @@ import { Tabs, Tag, Text, - useCustomDialogTrigger, ViewTypeValue } from '@/components' +import { useCustomDialogTrigger } from '@/context' import { BranchSelectorTab } from '@views/repo/components/branch-selector-v2/types' export interface FileViewerControlBarProps { diff --git a/packages/ui/src/components/file-explorer.tsx b/packages/ui/src/components/file-explorer.tsx index b47ce47305..c89c27b1c6 100644 --- a/packages/ui/src/components/file-explorer.tsx +++ b/packages/ui/src/components/file-explorer.tsx @@ -1,12 +1,32 @@ -import { ForwardedRef, forwardRef, ReactNode } from 'react' - -import { Accordion, GridProps, IconPropsV2, IconV2, Layout, Text, Tooltip, TooltipProps } from '@/components' +import { ForwardedRef, forwardRef, ReactNode, useMemo } from 'react' + +import { + Accordion, + Button, + ButtonProps, + GridProps, + IconPropsV2, + IconV2, + IconV2NamesType, + Layout, + Text, + Tooltip, + TooltipProps +} from '@/components' import { LinkProps, useRouterContext } from '@/context' import { cn } from '@utils/cn' +type SidebarItemActionButtonPropsType = ButtonProps & { + title?: string + iconName?: IconV2NamesType + iconProps?: Omit + onClick: React.MouseEventHandler +} + interface BaseItemProps { icon: NonNullable isActive?: boolean + actionButtons?: SidebarItemActionButtonPropsType[] } interface DefaultItemProps extends BaseItemProps, GridProps { @@ -22,16 +42,33 @@ interface LinkItemProps extends BaseItemProps, Omit { type ItemProps = DefaultItemProps | LinkItemProps const Item = forwardRef( - ({ className, children, icon, isActive, link, isFolder, ...props }: ItemProps, ref) => { + ({ className, children, icon, isActive, link, isFolder, actionButtons, ...props }: ItemProps, ref) => { const { Link } = useRouterContext() + const actionButtonsContent = useMemo(() => { + if (!actionButtons) return null + + return ( + + {actionButtons?.map((buttonProps, index) => { + const { title, iconOnly = true, iconName, iconProps, ...rest } = buttonProps + return ( + + ) + })} + + ) + }, [actionButtons]) + const commonClassnames = cn( - 'w-[fill-available] py-cn-2xs pr-1.5 rounded text-cn-2 hover:text-cn-1 focus-visible:text-cn-1 focus-visible:bg-cn-hover focus-visible:outline-none', + 'cn-file-tree-item', { - 'text-cn-1': isActive, - 'grid items-center justify-start gap-cn-2xs grid-flow-col': !!link, - 'bg-cn-selected': !isFolder && isActive, - 'hover:bg-cn-hover': !isFolder + 'cn-file-tree-item-wrapper cn-file-tree-item-leaf': !isFolder, + 'cn-file-tree-item-active': !isFolder && isActive, + 'cn-file-tree-item-with-action-buttons': !!actionButtonsContent }, className ) @@ -43,26 +80,19 @@ const Item = forwardRef( className={commonClassnames} {...(props as Omit)} > - - + + {children} + {actionButtonsContent} ) : ( - - - + + + {children} + {actionButtonsContent} ) } @@ -72,34 +102,29 @@ Item.displayName = 'FileExplorerItem' interface FolderItemProps { children: ReactNode level: number - value?: string + value: string isActive?: boolean content?: ReactNode + onClick?: (value: string) => void link?: string } -function FolderItem({ children, value = '', isActive, content, link, level }: FolderItemProps) { +function FolderItem({ children, value = '', isActive, content, link, onClick, ...otherProps }: FolderItemProps) { const itemElement = ( - + onClick?.(value)} {...otherProps}> {children} ) return ( - + @@ -109,9 +134,10 @@ function FolderItem({ children, value = '', isActive, content, link, level }: Fo {!!content && ( {content} + {/* LOADER */} )} @@ -123,19 +149,19 @@ type FileItemProps = { children: ReactNode isActive?: boolean link?: string - onClick?: () => void + value: string + onClick?: (value: string) => void tooltip?: TooltipProps['content'] [key: `data-${string}`]: any } -function FileItem({ children, isActive, level, link, onClick, tooltip, ...dataProps }: FileItemProps) { +function FileItem({ children, isActive, value, link, onClick, tooltip, ...dataProps }: FileItemProps) { const comp = ( onClick?.(value)} link={link} {...dataProps} > @@ -166,4 +192,4 @@ function Root({ children, onValueChange, value }: RootProps) { ) } -export { Root, FileItem, FolderItem } +export { Root, FileItem, FolderItem, Item } diff --git a/packages/ui/src/components/filters/types.ts b/packages/ui/src/components/filters/types.ts index af894061e4..07b3e6cf9e 100644 --- a/packages/ui/src/components/filters/types.ts +++ b/packages/ui/src/components/filters/types.ts @@ -19,6 +19,7 @@ export type SecretListFilters = { secretTypes?: CheckboxOptions[] secretManagerIdentifiers?: CheckboxOptions[] description?: string + tags?: string } export interface CheckboxOptions { diff --git a/packages/ui/src/components/form-input/index.ts b/packages/ui/src/components/form-input/index.ts index 233aace76e..514693a64b 100644 --- a/packages/ui/src/components/form-input/index.ts +++ b/packages/ui/src/components/form-input/index.ts @@ -15,6 +15,19 @@ const FormInput = { checked: field.value, onCheckedChange: field.onChange })), + /** + * Unlike other inputs, Select component's internal state is a generic type T, + * which is defined by the consumer of the component. Therefore, we need to + * explicitly define the type of the returned component here to ensure type safety. + * + * The `as` casting is necessary because `withForm` returns a generic component + * that doesn't inherently carry the specific type information of Select. + * By casting it, we inform TypeScript about the expected props and ref types. + * + * This ensures that when consumers use `FormInput.Select`, they get proper + * type checking and IntelliSense support for the Select component's props, + * including the generic type T. + */ Select: withForm(Select) as ( props: FormSelectProps & { ref?: ForwardedRef } ) => ReactElement, diff --git a/packages/ui/src/components/icon-v2/icon-name-map.ts b/packages/ui/src/components/icon-v2/icon-name-map.ts index 99abf6df4b..a8d56b6cbd 100644 --- a/packages/ui/src/components/icon-v2/icon-name-map.ts +++ b/packages/ui/src/components/icon-v2/icon-name-map.ts @@ -6,6 +6,7 @@ import AccountSolid from './icons/account-solid.svg' import Account from './icons/account.svg' import Agile from './icons/agile.svg' +import AiDeepResearch from './icons/ai-deep-research.svg' import AiMlOpsSolid from './icons/ai-ml-ops-solid.svg' import AiMlOps from './icons/ai-ml-ops.svg' import AiSolid from './icons/ai-solid.svg' @@ -14,7 +15,6 @@ import AiTest from './icons/ai-test.svg' import Ai from './icons/ai.svg' import AppDiscoverySolid from './icons/app-discovery-solid.svg' import AppDiscovery from './icons/app-discovery.svg' -import AppNotification from './icons/app-notification.svg' import AppSecurityTestsSolid from './icons/app-security-tests-solid.svg' import AppSecurityTests from './icons/app-security-tests.svg' import AppleShortcut from './icons/apple-shortcut.svg' @@ -44,25 +44,11 @@ import ArrowUp from './icons/arrow-up.svg' import ArrowsUpdown from './icons/arrows-updown.svg' import ArtifactsSolid from './icons/artifacts-solid.svg' import Artifacts from './icons/artifacts.svg' -import AtSignCircle from './icons/at-sign-circle.svg' import AtSign from './icons/at-sign.svg' import AttachmentImage from './icons/attachment-image.svg' import Attachment from './icons/attachment.svg' -import BatteryCharging from './icons/battery-charging.svg' -import BatteryEmpty from './icons/battery-empty.svg' -import BatteryFull from './icons/battery-full.svg' -import BatterySlash from './icons/battery-slash.svg' -import BatteryWarning from './icons/battery-warning.svg' -import Battery25 from './icons/battery25.svg' -import Battery50 from './icons/battery50.svg' -import Battery75 from './icons/battery75.svg' -import BellNotification from './icons/bell-notification.svg' import BellOff from './icons/bell-off.svg' import Bell from './icons/bell.svg' -import BinFull from './icons/bin-full.svg' -import BinHalf from './icons/bin-half.svg' -import BinMinusIn from './icons/bin-minus-in.svg' -import BinPlusIn from './icons/bin-plus-in.svg' import BoldSquere from './icons/bold-squere.svg' import Bold from './icons/bold.svg' import BookmarkSolid from './icons/bookmark-solid.svg' @@ -99,6 +85,9 @@ import ChatBubble from './icons/chat-bubble.svg' import ChatLines from './icons/chat-lines.svg' import ChatMinusIn from './icons/chat-minus-in.svg' import ChatPlusIn from './icons/chat-plus-in.svg' +import ChatPositionFull from './icons/chat-position-full.svg' +import ChatPositionLeft from './icons/chat-position-left.svg' +import ChatPositionRight from './icons/chat-position-right.svg' import CheckCircleSolid from './icons/check-circle-solid.svg' import CheckCircle from './icons/check-circle.svg' import Check from './icons/check.svg' @@ -113,7 +102,6 @@ import CloudCostsSolid from './icons/cloud-costs-solid.svg' import CloudCosts from './icons/cloud-costs.svg' import CloudDesync from './icons/cloud-desync.svg' import CloudDownload from './icons/cloud-download.svg' -import CloudSquare from './icons/cloud-square.svg' import CloudSync from './icons/cloud-sync.svg' import CloudUpload from './icons/cloud-upload.svg' import CloudXmark from './icons/cloud-xmark.svg' @@ -128,7 +116,6 @@ import CollapseSidebar from './icons/collapse-sidebar.svg' import Collapse from './icons/collapse.svg' import Community from './icons/community.svg' import Connectors from './icons/connectors.svg' -import ControlSlider from './icons/control-slider.svg' import Cookie from './icons/cookie.svg' import Copy from './icons/copy.svg' import CpuWarning from './icons/cpu-warning.svg' @@ -137,7 +124,6 @@ import Crop from './icons/crop.svg' import CursorPointer from './icons/cursor-pointer.svg' import CustomSecretManager from './icons/custom-secret-manager.svg' import CustomizeNavigation from './icons/customize-navigation.svg' -import DashboardDots from './icons/dashboard-dots.svg' import DashboardSolid from './icons/dashboard-solid.svg' import DashboardSpeed from './icons/dashboard-speed.svg' import Dashboard from './icons/dashboard.svg' @@ -160,10 +146,9 @@ import Delegates from './icons/delegates.svg' import DeploymentsSolid from './icons/deployments-solid.svg' import Deployments from './icons/deployments.svg' import Developer from './icons/developer.svg' -import DocMagnifyingGlassIn from './icons/doc-magnifying-glass-in.svg' import DocMagnifyingGlass from './icons/doc-magnifying-glass.svg' -import DocStarIn from './icons/doc-star-in.svg' import DocStar from './icons/doc-star.svg' +import Docs from './icons/docs.svg' import DoubleCheck from './icons/double-check.svg' import DownloadCircleSolid from './icons/download-circle-solid.svg' import DownloadCircle from './icons/download-circle.svg' @@ -173,7 +158,6 @@ import DragHandGesture from './icons/drag-hand-gesture.svg' import Drag from './icons/drag.svg' import EditPencil from './icons/edit-pencil.svg' import Edit from './icons/edit.svg' -import Eject from './icons/eject.svg' import EmptyPage from './icons/empty-page.svg' import EnergyUsageWindow from './icons/energy-usage-window.svg' import EngineeringInsightsSolid from './icons/engineering-insights-solid.svg' @@ -186,10 +170,9 @@ import Executions from './icons/executions.svg' import ExpandCode from './icons/expand-code.svg' import ExpandSidebar from './icons/expand-sidebar.svg' import Expand from './icons/expand.svg' -import Externaltickets from './icons/externaltickets.svg' +import ExternalTickets from './icons/external-tickets.svg' import EyeClosed from './icons/eye-closed.svg' import Eye from './icons/eye.svg' -import Facetime from './icons/facetime.svg' import FastArrowDown from './icons/fast-arrow-down.svg' import FastArrowLeft from './icons/fast-arrow-left.svg' import FastArrowRight from './icons/fast-arrow-right.svg' @@ -202,12 +185,14 @@ import FilterListCircle from './icons/filter-list-circle.svg' import FilterList from './icons/filter-list.svg' import FilterSolid from './icons/filter-solid.svg' import Filter from './icons/filter.svg' -import Finder from './icons/finder.svg' import FingerprintWindow from './icons/fingerprint-window.svg' import FlagSolid from './icons/flag-solid.svg' import FolderMinus from './icons/folder-minus.svg' +import FolderOpenedSolid from './icons/folder-opened-solid.svg' +import FolderOpened from './icons/folder-opened.svg' import FolderPlus from './icons/folder-plus.svg' import FolderSettings from './icons/folder-settings.svg' +import FolderSolid from './icons/folder-solid.svg' import FolderWarning from './icons/folder-warning.svg' import Folder from './icons/folder.svg' import ForwardMessage from './icons/forward-message.svg' @@ -225,12 +210,6 @@ import GitRebase from './icons/git-rebase.svg' import GitSquashMerge from './icons/git-squash-merge.svg' import GithubAction from './icons/github-action.svg' import Globe from './icons/globe.svg' -import GoogleDocs from './icons/google-docs.svg' -import GoogleDriveCheck from './icons/google-drive-check.svg' -import GoogleDriveSync from './icons/google-drive-sync.svg' -import GoogleDriveWarning from './icons/google-drive-warning.svg' -import GoogleDrive from './icons/google-drive.svg' -import GoogleOne from './icons/google-one.svg' import GripDots from './icons/grip-dots.svg' import Group1 from './icons/group-1.svg' import HalfMoon from './icons/half-moon.svg' @@ -249,12 +228,9 @@ import InfoCircleSolid from './icons/info-circle-solid.svg' import InfoCircle from './icons/info-circle.svg' import InfrastructureSolid from './icons/infrastructure-solid.svg' import Infrastructure from './icons/infrastructure.svg' -import InputSearch from './icons/input-search.svg' import Internet from './icons/internet.svg' import ItalicSquare from './icons/italic-square.svg' import Italic from './icons/italic.svg' -import JournalPage from './icons/journal-page.svg' -import Journal from './icons/journal.svg' import KanbanBoard from './icons/kanban-board.svg' import KeyBack from './icons/key-back.svg' import Key from './icons/key.svg' @@ -262,7 +238,6 @@ import LabelSolid from './icons/label-solid.svg' import Label from './icons/label.svg' import LaptopDevMode from './icons/laptop-dev-mode.svg' import LineSpace from './icons/line-space.svg' -import Linux from './icons/linux.svg' import ListSelect from './icons/list-select.svg' import List from './icons/list.svg' import Loader from './icons/loader.svg' @@ -279,9 +254,6 @@ import Maximize from './icons/maximize.svg' import MenuMoreHorizontal from './icons/menu-more-horizontal.svg' import MenuScale from './icons/menu-scale.svg' import Menu from './icons/menu.svg' -import MessageAlert from './icons/message-alert.svg' -import MessageText from './icons/message-text.svg' -import Message from './icons/message.svg' import MinusCircleSolid from './icons/minus-circle-solid.svg' import MinusCircle from './icons/minus-circle.svg' import MinusSquare from './icons/minus-square.svg' @@ -295,36 +267,17 @@ import MultiplePagesEmpty from './icons/multiple-pages-empty.svg' import MultiplePagesMinus from './icons/multiple-pages-minus.svg' import MultiplePagesPlus from './icons/multiple-pages-plus.svg' import MultiplePagesXmark from './icons/multiple-pages-xmark.svg' -import MultiplePages from './icons/multiple-pages.svg' import NavArrowDown from './icons/nav-arrow-down.svg' import NavArrowLeft from './icons/nav-arrow-left.svg' import NavArrowRight from './icons/nav-arrow-right.svg' import NavArrowUp from './icons/nav-arrow-up.svg' import NavSolidArrowDown from './icons/nav-solid-arrow-down.svg' +import NewThread from './icons/new-thread.svg' import Notes from './icons/notes.svg' -import Number1SquareSolid from './icons/number-1-square-solid.svg' -import Number1Square from './icons/number-1-square.svg' -import Number2SquareSolid from './icons/number-2-square-solid.svg' -import Number2Square from './icons/number-2-square.svg' -import Number3SquareSolid from './icons/number-3-square-solid.svg' -import Number3Square from './icons/number-3-square.svg' -import Number4SquareSolid from './icons/number-4-square-solid.svg' -import Number4Square from './icons/number-4-square.svg' -import Number5SquareSolid from './icons/number-5-square-solid.svg' -import Number5Square from './icons/number-5-square.svg' -import Number6SquareSolid from './icons/number-6-square-solid.svg' -import Number6Square from './icons/number-6-square.svg' -import Number7SquareSolid from './icons/number-7-square-solid.svg' -import Number7Square from './icons/number-7-square.svg' -import Number8SquareSolid from './icons/number-8-square-solid.svg' -import Number8Square from './icons/number-8-square.svg' -import Number9SquareSolid from './icons/number-9-square-solid.svg' -import Number9Square from './icons/number-9-square.svg' import NumberedListLeft from './icons/numbered-list-left.svg' import NumberedListRight from './icons/numbered-list-right.svg' import Okrs from './icons/okrs.svg' import OneFingerSelectHandGesture from './icons/one-finger-select-hand-gesture.svg' -import OpenInBrowser from './icons/open-in-browser.svg' import OpenInWindow from './icons/open-in-window.svg' import OpenNewWindow from './icons/open-new-window.svg' import OpenSelectHandGesture from './icons/open-select-hand-gesture.svg' @@ -332,28 +285,18 @@ import OrganizationSolid from './icons/organization-solid.svg' import Organizations from './icons/organizations.svg' import Overrides from './icons/overrides.svg' import PageDown from './icons/page-down.svg' -import PageEdit from './icons/page-edit.svg' import PageLeft from './icons/page-left.svg' import PageMinusIn from './icons/page-minus-in.svg' import PageMinus from './icons/page-minus.svg' import PagePlusIn from './icons/page-plus-in.svg' +import PagePlusMinusIn from './icons/page-plus-minus-in.svg' import PagePlus from './icons/page-plus.svg' import PageRight from './icons/page-right.svg' -import PageSearch from './icons/page-search.svg' -import PageStar from './icons/page-star.svg' import PageUp from './icons/page-up.svg' -import Page from './icons/page.svg' import PasteClipboard from './icons/paste-clipboard.svg' import PathArrow from './icons/path-arrow.svg' import Pause from './icons/pause.svg' import PeaceHand from './icons/peace-hand.svg' -import PhoneDisabled from './icons/phone-disabled.svg' -import PhoneIncome from './icons/phone-income.svg' -import PhoneMinus from './icons/phone-minus.svg' -import PhoneOutcome from './icons/phone-outcome.svg' -import PhonePaused from './icons/phone-paused.svg' -import PhonePlus from './icons/phone-plus.svg' -import PhoneXmark from './icons/phone-xmark.svg' import Phone from './icons/phone.svg' import PhysicalDataCenter from './icons/physical-data-center.svg' import PinSlashSolid from './icons/pin-slash-solid.svg' @@ -368,16 +311,13 @@ import PlusCircleSolid from './icons/plus-circle-solid.svg' import PlusCircle from './icons/plus-circle.svg' import PlusSquare from './icons/plus-square.svg' import Plus from './icons/plus.svg' -import Podcast from './icons/podcast.svg' import PortalSolid from './icons/portal-solid.svg' import Portal from './icons/portal.svg' import PrComment from './icons/pr-comment.svg' import Presentation from './icons/presentation.svg' -import PrivacyPolicy from './icons/privacy-policy.svg' import Prohibition from './icons/prohibition.svg' import Project from './icons/project.svg' import QuestionMark from './icons/question-mark.svg' -import QuoteMessage from './icons/quote-message.svg' import Quote from './icons/quote.svg' import RedoAction from './icons/redo-action.svg' import RedoCircleSolid from './icons/redo-circle-solid.svg' @@ -402,7 +342,7 @@ import RunTest from './icons/run-test.svg' import Run from './icons/run.svg' import RuntimeSecuritySolid from './icons/runtime-security-solid.svg' import RuntimeSecurity from './icons/runtime-security.svg' -import Safari from './icons/safari.svg' +import Satellite from './icons/satellite.svg' import ScissorAlt from './icons/scissor-alt.svg' import Scissor from './icons/scissor.svg' import SearchEngine from './icons/search-engine.svg' @@ -412,7 +352,6 @@ import SecurityPass from './icons/security-pass.svg' import SecurityTestsSolid from './icons/security-tests-solid.svg' import SecurityTests from './icons/security-tests.svg' import SelectWindow from './icons/select-window.svg' -import SendDiagonal from './icons/send-diagonal.svg' import SendMail from './icons/send-mail.svg' import Send from './icons/send.svg' import ServiceAccounts from './icons/service-accounts.svg' @@ -427,9 +366,9 @@ import SingleView from './icons/single-view.svg' import SlashSquare from './icons/slash-square.svg' import Slash from './icons/slash.svg' import Snowflake from './icons/snowflake.svg' -import Sort2Down from './icons/sort-2-down.svg' -import Sort2Up from './icons/sort-2-up.svg' import Sort2 from './icons/sort-2.svg' +import SortDown from './icons/sort-down.svg' +import SortUp from './icons/sort-up.svg' import SparksSolid from './icons/sparks-solid.svg' import Sparks from './icons/sparks.svg' import SplitView from './icons/split-view.svg' @@ -486,6 +425,7 @@ import UserBadgeCheck from './icons/user-badge-check.svg' import User from './icons/user.svg' import Variables from './icons/variables.svg' import Version from './icons/version.svg' +import VideoCall from './icons/video-call.svg' import ViewColumns2 from './icons/view-columns-2.svg' import ViewColumns3 from './icons/view-columns-3.svg' import ViewGrid from './icons/view-grid.svg' @@ -499,7 +439,6 @@ import Webhook from './icons/webhook.svg' import WorkspacesSolid from './icons/workspaces-solid.svg' import Workspaces from './icons/workspaces.svg' import WrapText from './icons/wrap-text.svg' -import Www from './icons/www.svg' import XmarkCircleSolid from './icons/xmark-circle-solid.svg' import XmarkCircle from './icons/xmark-circle.svg' import XmarkSquare from './icons/xmark-square.svg' @@ -511,6 +450,7 @@ export const IconNameMapV2 = { 'account-solid': AccountSolid, account: Account, agile: Agile, + 'ai-deep-research': AiDeepResearch, 'ai-ml-ops-solid': AiMlOpsSolid, 'ai-ml-ops': AiMlOps, 'ai-solid': AiSolid, @@ -519,7 +459,6 @@ export const IconNameMapV2 = { ai: Ai, 'app-discovery-solid': AppDiscoverySolid, 'app-discovery': AppDiscovery, - 'app-notification': AppNotification, 'app-security-tests-solid': AppSecurityTestsSolid, 'app-security-tests': AppSecurityTests, 'apple-shortcut': AppleShortcut, @@ -549,25 +488,11 @@ export const IconNameMapV2 = { 'arrows-updown': ArrowsUpdown, 'artifacts-solid': ArtifactsSolid, artifacts: Artifacts, - 'at-sign-circle': AtSignCircle, 'at-sign': AtSign, 'attachment-image': AttachmentImage, attachment: Attachment, - 'battery-charging': BatteryCharging, - 'battery-empty': BatteryEmpty, - 'battery-full': BatteryFull, - 'battery-slash': BatterySlash, - 'battery-warning': BatteryWarning, - battery25: Battery25, - battery50: Battery50, - battery75: Battery75, - 'bell-notification': BellNotification, 'bell-off': BellOff, bell: Bell, - 'bin-full': BinFull, - 'bin-half': BinHalf, - 'bin-minus-in': BinMinusIn, - 'bin-plus-in': BinPlusIn, 'bold-squere': BoldSquere, bold: Bold, 'bookmark-solid': BookmarkSolid, @@ -604,6 +529,9 @@ export const IconNameMapV2 = { 'chat-lines': ChatLines, 'chat-minus-in': ChatMinusIn, 'chat-plus-in': ChatPlusIn, + 'chat-position-full': ChatPositionFull, + 'chat-position-left': ChatPositionLeft, + 'chat-position-right': ChatPositionRight, 'check-circle-solid': CheckCircleSolid, 'check-circle': CheckCircle, check: Check, @@ -618,7 +546,6 @@ export const IconNameMapV2 = { 'cloud-costs': CloudCosts, 'cloud-desync': CloudDesync, 'cloud-download': CloudDownload, - 'cloud-square': CloudSquare, 'cloud-sync': CloudSync, 'cloud-upload': CloudUpload, 'cloud-xmark': CloudXmark, @@ -633,7 +560,6 @@ export const IconNameMapV2 = { collapse: Collapse, community: Community, connectors: Connectors, - 'control-slider': ControlSlider, cookie: Cookie, copy: Copy, 'cpu-warning': CpuWarning, @@ -642,7 +568,6 @@ export const IconNameMapV2 = { 'cursor-pointer': CursorPointer, 'custom-secret-manager': CustomSecretManager, 'customize-navigation': CustomizeNavigation, - 'dashboard-dots': DashboardDots, 'dashboard-solid': DashboardSolid, 'dashboard-speed': DashboardSpeed, dashboard: Dashboard, @@ -665,10 +590,9 @@ export const IconNameMapV2 = { 'deployments-solid': DeploymentsSolid, deployments: Deployments, developer: Developer, - 'doc-magnifying-glass-in': DocMagnifyingGlassIn, 'doc-magnifying-glass': DocMagnifyingGlass, - 'doc-star-in': DocStarIn, 'doc-star': DocStar, + docs: Docs, 'double-check': DoubleCheck, 'download-circle-solid': DownloadCircleSolid, 'download-circle': DownloadCircle, @@ -678,7 +602,6 @@ export const IconNameMapV2 = { drag: Drag, 'edit-pencil': EditPencil, edit: Edit, - eject: Eject, 'empty-page': EmptyPage, 'energy-usage-window': EnergyUsageWindow, 'engineering-insights-solid': EngineeringInsightsSolid, @@ -691,10 +614,9 @@ export const IconNameMapV2 = { 'expand-code': ExpandCode, 'expand-sidebar': ExpandSidebar, expand: Expand, - externaltickets: Externaltickets, + 'external-tickets': ExternalTickets, 'eye-closed': EyeClosed, eye: Eye, - facetime: Facetime, 'fast-arrow-down': FastArrowDown, 'fast-arrow-left': FastArrowLeft, 'fast-arrow-right': FastArrowRight, @@ -707,12 +629,14 @@ export const IconNameMapV2 = { 'filter-list': FilterList, 'filter-solid': FilterSolid, filter: Filter, - finder: Finder, 'fingerprint-window': FingerprintWindow, 'flag-solid': FlagSolid, 'folder-minus': FolderMinus, + 'folder-opened-solid': FolderOpenedSolid, + 'folder-opened': FolderOpened, 'folder-plus': FolderPlus, 'folder-settings': FolderSettings, + 'folder-solid': FolderSolid, 'folder-warning': FolderWarning, folder: Folder, 'forward-message': ForwardMessage, @@ -730,12 +654,6 @@ export const IconNameMapV2 = { 'git-squash-merge': GitSquashMerge, 'github-action': GithubAction, globe: Globe, - 'google-docs': GoogleDocs, - 'google-drive-check': GoogleDriveCheck, - 'google-drive-sync': GoogleDriveSync, - 'google-drive-warning': GoogleDriveWarning, - 'google-drive': GoogleDrive, - 'google-one': GoogleOne, 'grip-dots': GripDots, 'group-1': Group1, 'half-moon': HalfMoon, @@ -754,12 +672,9 @@ export const IconNameMapV2 = { 'info-circle': InfoCircle, 'infrastructure-solid': InfrastructureSolid, infrastructure: Infrastructure, - 'input-search': InputSearch, internet: Internet, 'italic-square': ItalicSquare, italic: Italic, - 'journal-page': JournalPage, - journal: Journal, 'kanban-board': KanbanBoard, 'key-back': KeyBack, key: Key, @@ -767,7 +682,6 @@ export const IconNameMapV2 = { label: Label, 'laptop-dev-mode': LaptopDevMode, 'line-space': LineSpace, - linux: Linux, 'list-select': ListSelect, list: List, loader: Loader, @@ -784,9 +698,6 @@ export const IconNameMapV2 = { 'menu-more-horizontal': MenuMoreHorizontal, 'menu-scale': MenuScale, menu: Menu, - 'message-alert': MessageAlert, - 'message-text': MessageText, - message: Message, 'minus-circle-solid': MinusCircleSolid, 'minus-circle': MinusCircle, 'minus-square': MinusSquare, @@ -800,36 +711,17 @@ export const IconNameMapV2 = { 'multiple-pages-minus': MultiplePagesMinus, 'multiple-pages-plus': MultiplePagesPlus, 'multiple-pages-xmark': MultiplePagesXmark, - 'multiple-pages': MultiplePages, 'nav-arrow-down': NavArrowDown, 'nav-arrow-left': NavArrowLeft, 'nav-arrow-right': NavArrowRight, 'nav-arrow-up': NavArrowUp, 'nav-solid-arrow-down': NavSolidArrowDown, + 'new-thread': NewThread, notes: Notes, - 'number-1-square-solid': Number1SquareSolid, - 'number-1-square': Number1Square, - 'number-2-square-solid': Number2SquareSolid, - 'number-2-square': Number2Square, - 'number-3-square-solid': Number3SquareSolid, - 'number-3-square': Number3Square, - 'number-4-square-solid': Number4SquareSolid, - 'number-4-square': Number4Square, - 'number-5-square-solid': Number5SquareSolid, - 'number-5-square': Number5Square, - 'number-6-square-solid': Number6SquareSolid, - 'number-6-square': Number6Square, - 'number-7-square-solid': Number7SquareSolid, - 'number-7-square': Number7Square, - 'number-8-square-solid': Number8SquareSolid, - 'number-8-square': Number8Square, - 'number-9-square-solid': Number9SquareSolid, - 'number-9-square': Number9Square, 'numbered-list-left': NumberedListLeft, 'numbered-list-right': NumberedListRight, okrs: Okrs, 'one-finger-select-hand-gesture': OneFingerSelectHandGesture, - 'open-in-browser': OpenInBrowser, 'open-in-window': OpenInWindow, 'open-new-window': OpenNewWindow, 'open-select-hand-gesture': OpenSelectHandGesture, @@ -837,28 +729,18 @@ export const IconNameMapV2 = { organizations: Organizations, overrides: Overrides, 'page-down': PageDown, - 'page-edit': PageEdit, 'page-left': PageLeft, 'page-minus-in': PageMinusIn, 'page-minus': PageMinus, 'page-plus-in': PagePlusIn, + 'page-plus-minus-in': PagePlusMinusIn, 'page-plus': PagePlus, 'page-right': PageRight, - 'page-search': PageSearch, - 'page-star': PageStar, 'page-up': PageUp, - page: Page, 'paste-clipboard': PasteClipboard, 'path-arrow': PathArrow, pause: Pause, 'peace-hand': PeaceHand, - 'phone-disabled': PhoneDisabled, - 'phone-income': PhoneIncome, - 'phone-minus': PhoneMinus, - 'phone-outcome': PhoneOutcome, - 'phone-paused': PhonePaused, - 'phone-plus': PhonePlus, - 'phone-xmark': PhoneXmark, phone: Phone, 'physical-data-center': PhysicalDataCenter, 'pin-slash-solid': PinSlashSolid, @@ -873,16 +755,13 @@ export const IconNameMapV2 = { 'plus-circle': PlusCircle, 'plus-square': PlusSquare, plus: Plus, - podcast: Podcast, 'portal-solid': PortalSolid, portal: Portal, 'pr-comment': PrComment, presentation: Presentation, - 'privacy-policy': PrivacyPolicy, prohibition: Prohibition, project: Project, 'question-mark': QuestionMark, - 'quote-message': QuoteMessage, quote: Quote, 'redo-action': RedoAction, 'redo-circle-solid': RedoCircleSolid, @@ -907,7 +786,7 @@ export const IconNameMapV2 = { run: Run, 'runtime-security-solid': RuntimeSecuritySolid, 'runtime-security': RuntimeSecurity, - safari: Safari, + satellite: Satellite, 'scissor-alt': ScissorAlt, scissor: Scissor, 'search-engine': SearchEngine, @@ -917,7 +796,6 @@ export const IconNameMapV2 = { 'security-tests-solid': SecurityTestsSolid, 'security-tests': SecurityTests, 'select-window': SelectWindow, - 'send-diagonal': SendDiagonal, 'send-mail': SendMail, send: Send, 'service-accounts': ServiceAccounts, @@ -932,9 +810,9 @@ export const IconNameMapV2 = { 'slash-square': SlashSquare, slash: Slash, snowflake: Snowflake, - 'sort-2-down': Sort2Down, - 'sort-2-up': Sort2Up, 'sort-2': Sort2, + 'sort-down': SortDown, + 'sort-up': SortUp, 'sparks-solid': SparksSolid, sparks: Sparks, 'split-view': SplitView, @@ -991,6 +869,7 @@ export const IconNameMapV2 = { user: User, variables: Variables, version: Version, + 'video-call': VideoCall, 'view-columns-2': ViewColumns2, 'view-columns-3': ViewColumns3, 'view-grid': ViewGrid, @@ -1004,7 +883,6 @@ export const IconNameMapV2 = { 'workspaces-solid': WorkspacesSolid, workspaces: Workspaces, 'wrap-text': WrapText, - www: Www, 'xmark-circle-solid': XmarkCircleSolid, 'xmark-circle': XmarkCircle, 'xmark-square': XmarkSquare, diff --git a/packages/ui/src/components/icon-v2/icons/ai-deep-research.svg b/packages/ui/src/components/icon-v2/icons/ai-deep-research.svg new file mode 100644 index 0000000000..d274adcab4 --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/ai-deep-research.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/chat-position-full.svg b/packages/ui/src/components/icon-v2/icons/chat-position-full.svg new file mode 100644 index 0000000000..d0fa68532c --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/chat-position-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/chat-position-left.svg b/packages/ui/src/components/icon-v2/icons/chat-position-left.svg new file mode 100644 index 0000000000..d13a143895 --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/chat-position-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/chat-position-right.svg b/packages/ui/src/components/icon-v2/icons/chat-position-right.svg new file mode 100644 index 0000000000..7c46d64831 --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/chat-position-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/doc-magnifying-glass.svg b/packages/ui/src/components/icon-v2/icons/doc-magnifying-glass.svg index 5aa1bf952b..2a2bd30919 100644 --- a/packages/ui/src/components/icon-v2/icons/doc-magnifying-glass.svg +++ b/packages/ui/src/components/icon-v2/icons/doc-magnifying-glass.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/doc-star.svg b/packages/ui/src/components/icon-v2/icons/doc-star.svg index 64ec7a0e2b..759e4dabba 100644 --- a/packages/ui/src/components/icon-v2/icons/doc-star.svg +++ b/packages/ui/src/components/icon-v2/icons/doc-star.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/docs.svg b/packages/ui/src/components/icon-v2/icons/docs.svg new file mode 100644 index 0000000000..496218143a --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/docs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/external-tickets.svg b/packages/ui/src/components/icon-v2/icons/external-tickets.svg new file mode 100644 index 0000000000..8ba95f0544 --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/external-tickets.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/folder-minus.svg b/packages/ui/src/components/icon-v2/icons/folder-minus.svg index c4b9f92ce7..bfee2cdef3 100644 --- a/packages/ui/src/components/icon-v2/icons/folder-minus.svg +++ b/packages/ui/src/components/icon-v2/icons/folder-minus.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/folder-opened-solid.svg b/packages/ui/src/components/icon-v2/icons/folder-opened-solid.svg new file mode 100644 index 0000000000..8745b98dd0 --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/folder-opened-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/folder-opened.svg b/packages/ui/src/components/icon-v2/icons/folder-opened.svg new file mode 100644 index 0000000000..f71ef55cc5 --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/folder-opened.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/folder-plus.svg b/packages/ui/src/components/icon-v2/icons/folder-plus.svg index 82f0ed94c7..a666be7636 100644 --- a/packages/ui/src/components/icon-v2/icons/folder-plus.svg +++ b/packages/ui/src/components/icon-v2/icons/folder-plus.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/folder-solid.svg b/packages/ui/src/components/icon-v2/icons/folder-solid.svg new file mode 100644 index 0000000000..8b7f3bcaf9 --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/folder-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/headset-help.svg b/packages/ui/src/components/icon-v2/icons/headset-help.svg index 3c2b7d75a5..707ea6e277 100644 --- a/packages/ui/src/components/icon-v2/icons/headset-help.svg +++ b/packages/ui/src/components/icon-v2/icons/headset-help.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/internet.svg b/packages/ui/src/components/icon-v2/icons/internet.svg index f170769f94..68b9cf44b0 100644 --- a/packages/ui/src/components/icon-v2/icons/internet.svg +++ b/packages/ui/src/components/icon-v2/icons/internet.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/mail-out.svg b/packages/ui/src/components/icon-v2/icons/mail-out.svg index b6335bdd9c..d230e1d3e2 100644 --- a/packages/ui/src/components/icon-v2/icons/mail-out.svg +++ b/packages/ui/src/components/icon-v2/icons/mail-out.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/new-thread.svg b/packages/ui/src/components/icon-v2/icons/new-thread.svg new file mode 100644 index 0000000000..ed0b2d794a --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/new-thread.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/page-plus-minus-in.svg b/packages/ui/src/components/icon-v2/icons/page-plus-minus-in.svg new file mode 100644 index 0000000000..17627eecd0 --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/page-plus-minus-in.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/satellite.svg b/packages/ui/src/components/icon-v2/icons/satellite.svg new file mode 100644 index 0000000000..c0e9f679e2 --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/satellite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/send.svg b/packages/ui/src/components/icon-v2/icons/send.svg index f9007f1e07..b316937b98 100644 --- a/packages/ui/src/components/icon-v2/icons/send.svg +++ b/packages/ui/src/components/icon-v2/icons/send.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/sort-down.svg b/packages/ui/src/components/icon-v2/icons/sort-down.svg new file mode 100644 index 0000000000..0bfb030fc4 --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/sort-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/sort-up.svg b/packages/ui/src/components/icon-v2/icons/sort-up.svg new file mode 100644 index 0000000000..26605dd38f --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/sort-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-v2/icons/video-call.svg b/packages/ui/src/components/icon-v2/icons/video-call.svg new file mode 100644 index 0000000000..81f728601d --- /dev/null +++ b/packages/ui/src/components/icon-v2/icons/video-call.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/icon-with-tooltip.tsx b/packages/ui/src/components/icon-with-tooltip.tsx index 07f4392ce9..d4c9d6c1a3 100644 --- a/packages/ui/src/components/icon-with-tooltip.tsx +++ b/packages/ui/src/components/icon-with-tooltip.tsx @@ -15,7 +15,7 @@ export interface IconWithTooltipProps extends Omit { export const IconWithTooltip = ({ className, disabled, iconProps, ...props }: IconWithTooltipProps) => ( ) diff --git a/packages/ui/src/components/illustration/images/no-data-folder-light.svg b/packages/ui/src/components/illustration/images/no-data-folder-light.svg index cc8e60ec35..9c0db226d9 100644 --- a/packages/ui/src/components/illustration/images/no-data-folder-light.svg +++ b/packages/ui/src/components/illustration/images/no-data-folder-light.svg @@ -1,72 +1,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/components/illustration/images/no-data-folder.svg b/packages/ui/src/components/illustration/images/no-data-folder.svg index 2a5ebe4a1f..65c4ffe528 100644 --- a/packages/ui/src/components/illustration/images/no-data-folder.svg +++ b/packages/ui/src/components/illustration/images/no-data-folder.svg @@ -1,77 +1,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index f2c45fab26..3c12612816 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -7,6 +7,7 @@ export * from './drawer' export * from './text' export * from './spacer' export * from './pagination/pagination' +export type * from './pagination/types' export * from './status-badge/status-badge' export * from './counter-badge' export * from './scroll-area' @@ -20,7 +21,6 @@ export * from './no-data' export * from './tabs' export * from './command' export * from './search-files' -export * from './toast' export * from './link' export * from './commit-copy-actions' export * from './accordion/accordion' @@ -59,7 +59,6 @@ export * from './filters' export * from './more-actions-tooltip' export * from './split-button' export * from './tooltip' -export * from './navigation-menu' export * from './pipeline-nodes' export * from './sidebar' export * from './separator' @@ -101,3 +100,6 @@ export * from './form-input' export * from './favorite' export * from './scope' export * from './rbac' + +// Sonner Toast +export * from './toast' diff --git a/packages/ui/src/components/input-otp.tsx b/packages/ui/src/components/input-otp.tsx index d6f825b066..957ad55642 100644 --- a/packages/ui/src/components/input-otp.tsx +++ b/packages/ui/src/components/input-otp.tsx @@ -1,9 +1,10 @@ import { ComponentPropsWithoutRef, ElementRef, forwardRef, useContext } from 'react' -import { DashIcon } from '@radix-ui/react-icons' import { cn } from '@utils/cn' import { OTPInput, OTPInputContext } from 'input-otp' +import { IconV2 } from './icon-v2' + const InputOTPRoot = forwardRef, ComponentPropsWithoutRef>( ({ className, containerClassName, ...props }, ref) => ( (({ index, cla {char} {hasFakeCaret && (
-
+
)}
@@ -58,7 +59,7 @@ InputOTPSlot.displayName = 'InputOTPSlot' const InputOTPSeparator = forwardRef, ComponentPropsWithoutRef<'div'>>(({ ...props }, ref) => (
- +
)) InputOTPSeparator.displayName = 'InputOTPSeparator' diff --git a/packages/ui/src/components/inputs/number-input.tsx b/packages/ui/src/components/inputs/number-input.tsx index 6b67cf17e1..cfa948ba6a 100644 --- a/packages/ui/src/components/inputs/number-input.tsx +++ b/packages/ui/src/components/inputs/number-input.tsx @@ -137,10 +137,10 @@ export const NumberInput = forwardRef( (!isStepperDisabled || !!suffix) && (
{!isStepperDisabled ? ( -
+
{items && ( diff --git a/packages/ui/src/components/logo-v2/logo-name-map.ts b/packages/ui/src/components/logo-v2/logo-name-map.ts index 7954f58db6..84e9ef6e32 100644 --- a/packages/ui/src/components/logo-v2/logo-name-map.ts +++ b/packages/ui/src/components/logo-v2/logo-name-map.ts @@ -48,6 +48,7 @@ import Npm from './logos/npm.svg' import Oci from './logos/oci.svg' import OpenShift from './logos/open-shift.svg' import OpenTofu from './logos/open-tofu.svg' +import Opsgenie from './logos/opsgenie.svg' import Pagerduty from './logos/pagerduty.svg' import Prometheus from './logos/prometheus.svg' import Python from './logos/python.svg' @@ -111,6 +112,7 @@ export const LogoNameMapV2 = { oci: Oci, 'open-shift': OpenShift, 'open-tofu': OpenTofu, + opsgenie: Opsgenie, pagerduty: Pagerduty, prometheus: Prometheus, python: Python, diff --git a/packages/ui/src/components/logo-v2/logos/argo.svg b/packages/ui/src/components/logo-v2/logos/argo.svg index e6af4b444e..b9317e0391 100644 --- a/packages/ui/src/components/logo-v2/logos/argo.svg +++ b/packages/ui/src/components/logo-v2/logos/argo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/logo-v2/logos/grype.svg b/packages/ui/src/components/logo-v2/logos/grype.svg index 3b46e12d7f..bfc45b0f56 100644 --- a/packages/ui/src/components/logo-v2/logos/grype.svg +++ b/packages/ui/src/components/logo-v2/logos/grype.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/logo-v2/logos/jenkins-x.svg b/packages/ui/src/components/logo-v2/logos/jenkins-x.svg index 7b5829aa8d..f10e91ccab 100644 --- a/packages/ui/src/components/logo-v2/logos/jenkins-x.svg +++ b/packages/ui/src/components/logo-v2/logos/jenkins-x.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/logo-v2/logos/katalon.svg b/packages/ui/src/components/logo-v2/logos/katalon.svg index 3e09592815..8fff442534 100644 --- a/packages/ui/src/components/logo-v2/logos/katalon.svg +++ b/packages/ui/src/components/logo-v2/logos/katalon.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/logo-v2/logos/linux.svg b/packages/ui/src/components/logo-v2/logos/linux.svg index e017d2e7b8..1ada94b405 100644 --- a/packages/ui/src/components/logo-v2/logos/linux.svg +++ b/packages/ui/src/components/logo-v2/logos/linux.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/logo-v2/logos/new-relic.svg b/packages/ui/src/components/logo-v2/logos/new-relic.svg index db52280fa1..f2278da341 100644 --- a/packages/ui/src/components/logo-v2/logos/new-relic.svg +++ b/packages/ui/src/components/logo-v2/logos/new-relic.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/logo-v2/logos/open-tofu.svg b/packages/ui/src/components/logo-v2/logos/open-tofu.svg index 536dd62f7f..c6b6efaae0 100644 --- a/packages/ui/src/components/logo-v2/logos/open-tofu.svg +++ b/packages/ui/src/components/logo-v2/logos/open-tofu.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/logo-v2/logos/opsgenie.svg b/packages/ui/src/components/logo-v2/logos/opsgenie.svg new file mode 100644 index 0000000000..bacc905611 --- /dev/null +++ b/packages/ui/src/components/logo-v2/logos/opsgenie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/components/logo-v2/logos/servicenow.svg b/packages/ui/src/components/logo-v2/logos/servicenow.svg index 96d2c11372..81cb79077b 100644 --- a/packages/ui/src/components/logo-v2/logos/servicenow.svg +++ b/packages/ui/src/components/logo-v2/logos/servicenow.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/logo-v2/logos/terragrunt.svg b/packages/ui/src/components/logo-v2/logos/terragrunt.svg index 57c23c3bba..325934b7b4 100644 --- a/packages/ui/src/components/logo-v2/logos/terragrunt.svg +++ b/packages/ui/src/components/logo-v2/logos/terragrunt.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/logo-v2/logos/vue-js.svg b/packages/ui/src/components/logo-v2/logos/vue-js.svg index 3d94a25bf7..435d49c294 100644 --- a/packages/ui/src/components/logo-v2/logos/vue-js.svg +++ b/packages/ui/src/components/logo-v2/logos/vue-js.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/src/components/manage-navigation/index.tsx b/packages/ui/src/components/manage-navigation/index.tsx index 2f7559b30c..932a6cb2fc 100644 --- a/packages/ui/src/components/manage-navigation/index.tsx +++ b/packages/ui/src/components/manage-navigation/index.tsx @@ -7,11 +7,11 @@ import { closestCenter, DndContext } from '@dnd-kit/core' import { SortableContext } from '@dnd-kit/sortable' import { DraggableItem } from './draggable-item' -import { ManageNavigationSearch } from './manage-navigation-search' interface ManageNavigationProps { pinnedItems: NavbarItemType[] - navbarMenuData: MenuGroupType[] + // Might be needed for search later + navbarMenuData?: MenuGroupType[] showManageNavigation: boolean recentItems: NavbarItemType[] onSave: (recentItems: NavbarItemType[], currentPinnedItems: NavbarItemType[]) => void @@ -24,19 +24,9 @@ const filterRecentItems = (pinnedItems: NavbarItemType[], recentItems: NavbarIte return recentItems.filter(item => !pinnedItems.some(pinnedItem => pinnedItem.id === item.id)) } -const filterMenuData = (menuData: MenuGroupType[], pinnedItems: NavbarItemType[]): MenuGroupType[] => { - return menuData - .map(group => ({ - ...group, - items: group.items.filter(item => !pinnedItems.some(pinnedItem => pinnedItem.id === item.id)) - })) - .filter(group => group.items.length > 0) -} - export const ManageNavigation = ({ pinnedItems, recentItems, - navbarMenuData, showManageNavigation, onSave, onClose, @@ -121,16 +111,14 @@ export const ManageNavigation = ({ - Pinned {!currentPinnedItems.length ? ( - No pinned items + + No pinned items + ) : ( diff --git a/packages/ui/src/components/markdown-viewer/style.css b/packages/ui/src/components/markdown-viewer/style.css index 2274bbe759..f2c64b58e3 100644 --- a/packages/ui/src/components/markdown-viewer/style.css +++ b/packages/ui/src/components/markdown-viewer/style.css @@ -251,7 +251,7 @@ @apply font-body-code bg-cn-1 border-cn-3 text-cn-1 mb-0 rounded-none border-0 p-0; code { - @apply bg-cn-1 !text-cn-1 rounded-none p-0; + @apply bg-cn-2 !text-cn-1 rounded-none p-0; } } diff --git a/packages/ui/src/components/more-actions-tooltip.tsx b/packages/ui/src/components/more-actions-tooltip.tsx index 5537d820ac..88f568c8ee 100644 --- a/packages/ui/src/components/more-actions-tooltip.tsx +++ b/packages/ui/src/components/more-actions-tooltip.tsx @@ -1,7 +1,7 @@ import { forwardRef } from 'react' import { useRouterContext } from '@/context' -import { Button, ButtonProps, ButtonVariants } from '@components/button' +import { Button, ButtonProps, ButtonSizes, ButtonVariants } from '@components/button' import { DropdownMenu } from '@components/dropdown-menu' import { IconV2, type IconPropsV2 } from '@components/icon-v2' import { Tooltip, TooltipProps } from '@components/tooltip' @@ -28,6 +28,8 @@ export interface MoreActionsTooltipProps { alignOffset?: number className?: string buttonVariant?: ButtonVariants + buttonSize?: ButtonSizes + disabled?: boolean } /** @@ -35,7 +37,17 @@ export interface MoreActionsTooltipProps { */ export const MoreActionsTooltip = forwardRef( ( - { theme, actions, iconName = 'more-vert', sideOffset = 2, alignOffset = 0, className, buttonVariant = 'ghost' }, + { + theme, + actions, + iconName = 'more-vert', + sideOffset = 2, + alignOffset = 0, + className, + buttonVariant = 'ghost', + buttonSize = 'md', + disabled = false + }, ref ) => { const { Link } = useRouterContext() @@ -43,15 +55,17 @@ export const MoreActionsTooltip = forwardRef - + diff --git a/packages/ui/src/components/multi-select.tsx b/packages/ui/src/components/multi-select.tsx index 791076e761..45c7b749dc 100644 --- a/packages/ui/src/components/multi-select.tsx +++ b/packages/ui/src/components/multi-select.tsx @@ -24,6 +24,7 @@ import { Tag } from '@/components' import { generateAlphaNumericHash } from '@/utils' +import { csvToObject } from '@/utils/stringUtils' import { useDebounceSearch } from '@hooks/use-debounce-search' import { cn } from '@utils/cn' import { cva, VariantProps } from 'class-variance-authority' @@ -68,33 +69,6 @@ export interface MultiSelectOption { onReset?: () => void } -/** - * Creates a new MultiSelectOption from an input string - * Handles both simple values and key:value format - * @param inputValue The string value entered by the user - * @returns A properly formatted MultiSelectOption - */ -const createOptionFromInput = (inputValue: string): MultiSelectOption => { - // Handle key:value format (e.g. "category:frontend") - if (inputValue.includes(':')) { - const [key, value] = inputValue.split(':', 2) - - if (key && key.trim()) { - return { - key: key.trim(), - value: value ? value.trim() : '', - id: inputValue - } - } - } - - // Default case: use input as the key - return { - key: inputValue, - id: inputValue - } -} - export interface MultiSelectProps extends CommonInputsProp { value?: MultiSelectOption[] defaultValue?: MultiSelectOption[] @@ -220,24 +194,40 @@ export const MultiSelect = forwardRef( } } if (e.key === 'Enter' && input.value && !disallowCreation) { - // Perform case-insensitive comparison to prevent duplicate options with different casing - // This ensures that 'React' and 'react' would be considered the same option - if ( - !options?.some(option => option.key.toLowerCase() === input.value.toLowerCase()) && - (!availableOptions || availableOptions.length === 0) && - !getSelectedOptions().some(s => s.key.toLowerCase() === input.value.toLowerCase()) - ) { - const newOption = createOptionFromInput(input.value) - const newOptions = [...getSelectedOptions(), newOption] - if (isControlled) { - onChange?.(newOptions) + const inputValue = input.value.trim() + // Handle comma-separated input or single option + const { data: csvData, metadata: csvMetadata } = csvToObject(inputValue) + const updatedOptions = getSelectedOptions() + + // Process each key-value pair from the CSV object + for (const [key, value] of Object.entries(csvData)) { + const wasKeyValuePair = csvMetadata[key] // Use metadata from csvToObject + const newOption = { + key, + value: wasKeyValuePair ? value : undefined, // Set value only for genuine key:value pairs + id: wasKeyValuePair ? `${key}:${value}` : key + } + + const existingIndex = updatedOptions.findIndex(option => option.key === newOption.key) + + if (existingIndex !== -1) { + // Replace existing option + updatedOptions[existingIndex] = newOption } else { - setSelected(newOptions) + // Add new option + updatedOptions.push(newOption) } - setInputValue('') - setSearchQuery?.('') - e.preventDefault() } + + // Update state and clear input + if (isControlled) { + onChange?.(updatedOptions) + } else { + setSelected(updatedOptions) + } + setInputValue('') + setSearchQuery?.('') + e.preventDefault() } if (e.key === 'Escape') { input.blur() diff --git a/packages/ui/src/components/navigation-menu.tsx b/packages/ui/src/components/navigation-menu.tsx deleted file mode 100644 index 09b42385c5..0000000000 --- a/packages/ui/src/components/navigation-menu.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import * as React from 'react' - -import { ChevronDownIcon } from '@radix-ui/react-icons' -import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu' -import { cn } from '@utils/cn' -import { cva } from 'class-variance-authority' - -const NavigationMenuRoot = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - {children} - - -)) -NavigationMenuRoot.displayName = NavigationMenuPrimitive.Root.displayName - -const NavigationMenuList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName - -const NavigationMenuItem = NavigationMenuPrimitive.Item - -const navigationMenuTriggerStyle = cva( - 'group inline-flex h-9 w-max items-center justify-center rounded-md bg-cn-1 px-4 py-2 text-sm font-medium transition-colors hover:bg-cn-3 hover:text-cn-1 focus:bg-cn-3 focus:text-cn-1 focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-cn-3/50 data-[state=open]:bg-cn-3/50' -) - -const NavigationMenuTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - {children}{' '} - -)) -NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName - -const NavigationMenuContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName - -const NavigationMenuLink = NavigationMenuPrimitive.Link - -const NavigationMenuViewport = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( -
- -
-)) -NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName - -const NavigationMenuIndicator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -
- -)) -NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName - -const NavigationMenu = { - Root: NavigationMenuRoot, - List: NavigationMenuList, - Item: NavigationMenuItem, - Content: NavigationMenuContent, - Trigger: NavigationMenuTrigger, - Link: NavigationMenuLink, - Indicator: NavigationMenuIndicator, - Viewport: NavigationMenuViewport -} - -export { NavigationMenu } diff --git a/packages/ui/src/components/no-data.tsx b/packages/ui/src/components/no-data.tsx index 8e36c61fba..d00037b27f 100644 --- a/packages/ui/src/components/no-data.tsx +++ b/packages/ui/src/components/no-data.tsx @@ -1,4 +1,4 @@ -import { Dispatch, FC, Fragment, ReactNode, SetStateAction } from 'react' +import { Dispatch, FC, Fragment, ReactNode, Ref, SetStateAction } from 'react' import { Button, @@ -10,8 +10,7 @@ import { IllustrationsNameType, Layout, SplitButton, - Text, - toButtonProps + Text } from '@/components' import { useRouterContext } from '@/context' import { cn } from '@utils/cn' @@ -23,12 +22,14 @@ export interface NoDataProps { imageSize?: number description: string[] primaryButton?: ButtonProps & { + ref?: Ref icon?: IconPropsV2['name'] label: ReactNode | string to?: string isDialogTrigger?: boolean } secondaryButton?: ButtonProps & { + ref?: Ref icon?: IconPropsV2['name'] label: ReactNode | string to?: string @@ -72,7 +73,7 @@ export const NoData: FC = ({ align="center" justify="center" className={cn( - 'h-full w-full my-auto py-cn-4xl', + 'h-full w-full my-auto py-cn-4xl px-cn-2xl', { 'h-auto grow border border-cn-3 rounded-md': withBorder }, className )} @@ -92,7 +93,7 @@ export const NoData: FC = ({ {primaryButton && (primaryButton.to ? ( - ) : ( - @@ -108,11 +109,7 @@ export const NoData: FC = ({ ))} {secondaryButton && (secondaryButton.to ? ( - - - ) : null} - - - - {!isLoading && !semanticEnabled && stats && results && results.length > 0 && ( - - Results: - - {t('views:search.statsText', '{{matchCount}} matches found in {{fileCount}} file(s)', { - matchCount: stats.total_matches, - fileCount: stats.total_files - })} - - - )} - {!isLoading && semanticEnabled && stats && semanticResults && semanticResults.length > 0 && ( - - Results: - - {t('views:search.statsText', '{{fileCount}} file(s)', { - fileCount: stats.total_files - })} - - - )} - - - - {semanticEnabled ? ( - - ) : ( - - )} - - + ) : null} + + + {!isLoading && !semanticEnabled && stats && results && results.length > 0 && ( + + Results:{' '} + + {t( + 'views:search.statsText.matchesAndFiles', + '{{matchCount}} matches found in {{fileCount}} file(s)', + { matchCount: stats.total_matches, fileCount: stats.total_files } + )} + + + )} + {!isLoading && semanticEnabled && stats && semanticResults && semanticResults.length > 0 && ( + + Results:{' '} + + {t('views:search.statsText.files', '{{fileCount}} file(s)', { fileCount: stats.total_files })} + + + )} + + {semanticEnabled ? ( + + ) : ( + + )} + + ) diff --git a/packages/ui/src/views/secrets/create-secret/file-upload.tsx b/packages/ui/src/views/secrets/create-secret/file-upload.tsx index c8d6f02757..37f7d899d0 100644 --- a/packages/ui/src/views/secrets/create-secret/file-upload.tsx +++ b/packages/ui/src/views/secrets/create-secret/file-upload.tsx @@ -69,7 +69,7 @@ export function FileUpload({ selectedFile, onFileChange, error, accept }: FileUp
- +
diff --git a/packages/ui/src/views/secrets/secret-details/secret-details-activity-list.tsx b/packages/ui/src/views/secrets/secret-details/secret-details-activity-list.tsx index aefd32e792..9daa088dd5 100644 --- a/packages/ui/src/views/secrets/secret-details/secret-details-activity-list.tsx +++ b/packages/ui/src/views/secrets/secret-details/secret-details-activity-list.tsx @@ -1,4 +1,4 @@ -import { Layout, NoData, ScopeTag, Skeleton, Table, Text, TimeAgoCard } from '@/components' +import { NoData, ScopeTag, Skeleton, Table, Text, TimeAgoCard } from '@/components' import { useTranslation } from '@/context' import { SecretActivity } from './types' @@ -6,35 +6,50 @@ import { SecretActivity } from './types' interface SecretActivityListProps { secretActivity: SecretActivity[] isLoading: boolean + isDirtyList: boolean + handleResetFiltersQueryAndPages: () => void } -export function SecretActivityList({ secretActivity, isLoading }: SecretActivityListProps): JSX.Element { +export function SecretActivityList({ + secretActivity, + isLoading, + isDirtyList, + handleResetFiltersQueryAndPages +}: SecretActivityListProps): JSX.Element { const { t } = useTranslation() if (isLoading) { return } - if (!secretActivity.length) { + if (!secretActivity.length && isDirtyList) { return ( ) } return ( - + - {t('views:secretActivity.event', 'Event')} + {t('views:secretActivity.event', 'Event')} {t('views:secretActivity.entity', 'Entity')} {t('views:secretActivity.scope', 'Scope')} {t('views:secretActivity.timestamp', 'Timestamp')} @@ -42,33 +57,23 @@ export function SecretActivityList({ secretActivity, isLoading }: SecretActivity {secretActivity.map(activity => ( - - - + + + {activity.event} + {activity?.entityRenderer ?? {activity.type}} - {activity.entityRenderer ? ( - activity.entityRenderer - ) : ( - - - {activity.type} - - - )} - - - + - - {activity?.createdAt ? ( + + {!!activity?.createdAt && ( - ) : null} + )} ))} diff --git a/packages/ui/src/views/secrets/secret-details/secret-details-activity-page.tsx b/packages/ui/src/views/secrets/secret-details/secret-details-activity-page.tsx index 304e25cb77..4b8960e9ea 100644 --- a/packages/ui/src/views/secrets/secret-details/secret-details-activity-page.tsx +++ b/packages/ui/src/views/secrets/secret-details/secret-details-activity-page.tsx @@ -1,17 +1,14 @@ -import { FC } from 'react' +import { FC, useCallback, useMemo } from 'react' -import { IconV2, ListActions, NoData, Pagination, SearchBox, Spacer } from '@/components' +import { IconV2, Layout, ListActions, NoData, Pagination, SearchInput } from '@/components' import { useRouterContext, useTranslation } from '@/context' -import { useDebounceSearch } from '@/hooks' -import { cn } from '@utils/cn' -import { SandboxLayout } from '@views/layouts/SandboxLayout' import { SecretActivityList } from './secret-details-activity-list' import { SecretActivity } from './types' interface SecretActivityPageProps { searchQuery: string - setSearchQuery: (query: string) => void + setSearchQuery: (query: string | undefined) => void isError: boolean errorMessage: string currentPage: number @@ -37,10 +34,21 @@ const SecretActivityPage: FC = ({ const { t } = useTranslation() const { navigate } = useRouterContext() - const { search: searchInput, handleSearchChange: handleInputChange } = useDebounceSearch({ - handleChangeSearchValue: setSearchQuery, - searchValue: searchQuery || '' - }) + const handleSearchChange = useCallback( + (query: string) => { + setSearchQuery(query.length ? query : undefined) + goToPage(1) + }, + [goToPage, setSearchQuery] + ) + + const isDirtyList = useMemo(() => { + return currentPage !== 1 || !!searchQuery.length + }, [currentPage, searchQuery]) + + const isShowPagination = useMemo(() => { + return !isLoading && !!secretActivity.length + }, [isLoading, secretActivity]) if (isError) { return ( @@ -72,26 +80,42 @@ const SecretActivityPage: FC = ({ ) } + if (!secretActivity.length && !isLoading && !isDirtyList) { + return ( + + ) + } + return ( - - - - - - - - - - - + <> + + + + + + + + handleSearchChange('')} + /> + {isShowPagination && ( + + )} + ) } diff --git a/packages/ui/src/views/secrets/secret-details/secret-details-references-list.tsx b/packages/ui/src/views/secrets/secret-details/secret-details-references-list.tsx index 2a54ca7f34..817531ab70 100644 --- a/packages/ui/src/views/secrets/secret-details/secret-details-references-list.tsx +++ b/packages/ui/src/views/secrets/secret-details/secret-details-references-list.tsx @@ -6,34 +6,50 @@ import { SecretReference } from './types' interface SecretReferencesListProps { secretReferences: SecretReference[] isLoading: boolean + isDirtyList: boolean + handleResetFiltersQueryAndPages: () => void } -export function SecretReferencesList({ secretReferences, isLoading }: SecretReferencesListProps): JSX.Element { +export function SecretReferencesList({ + secretReferences, + isLoading, + isDirtyList, + handleResetFiltersQueryAndPages +}: SecretReferencesListProps): JSX.Element { const { t } = useTranslation() if (isLoading) { return } - if (!secretReferences.length) { + if (!secretReferences.length && isDirtyList) { return ( ) } return ( - + - {t('views:entityReference.entity', 'Entity')} + {t('views:entityReference.entity', 'Entity')} {t('views:entityReference.type', 'Type')} {t('views:entityReference.scope', 'Scope')} {t('views:entityReference.created', 'Created')} @@ -42,29 +58,27 @@ export function SecretReferencesList({ secretReferences, isLoading }: SecretRefe {secretReferences.map(reference => ( - - + + {reference.name} - - - - {reference.type} - + + + {reference.type} - - + + - - {reference?.createdAt ? ( + + {!!reference?.createdAt && ( - ) : null} + )} ))} diff --git a/packages/ui/src/views/secrets/secret-details/secret-details-references-page.tsx b/packages/ui/src/views/secrets/secret-details/secret-details-references-page.tsx index ad98ec3e4c..cbf7234077 100644 --- a/packages/ui/src/views/secrets/secret-details/secret-details-references-page.tsx +++ b/packages/ui/src/views/secrets/secret-details/secret-details-references-page.tsx @@ -1,17 +1,14 @@ -import { FC } from 'react' +import { FC, useCallback, useMemo } from 'react' -import { IconV2, ListActions, NoData, Pagination, SearchBox, Spacer } from '@/components' +import { IconV2, Layout, ListActions, NoData, Pagination, SearchInput } from '@/components' import { useRouterContext, useTranslation } from '@/context' -import { useDebounceSearch } from '@/hooks' -import { cn } from '@utils/cn' -import { SandboxLayout } from '@views/layouts/SandboxLayout' import { SecretReferencesList } from './secret-details-references-list' import { SecretReference } from './types' interface SecretsReferencePageProps { searchQuery: string - setSearchQuery: (query: string) => void + setSearchQuery: (query: string | undefined) => void isError: boolean errorMessage: string currentPage: number @@ -37,10 +34,21 @@ const SecretReferencesPage: FC = ({ const { t } = useTranslation() const { navigate } = useRouterContext() - const { search: searchInput, handleSearchChange: handleInputChange } = useDebounceSearch({ - handleChangeSearchValue: setSearchQuery, - searchValue: searchQuery || '' - }) + const handleSearchChange = useCallback( + (query: string) => { + setSearchQuery(query.length ? query : undefined) + goToPage(1) + }, + [goToPage, setSearchQuery] + ) + + const isDirtyList = useMemo(() => { + return currentPage !== 1 || !!searchQuery.length + }, [currentPage, searchQuery]) + + const isShowPagination = useMemo(() => { + return !isLoading && !!secretReferences.length + }, [isLoading, secretReferences]) if (isError) { return ( @@ -72,26 +80,42 @@ const SecretReferencesPage: FC = ({ ) } + if (!secretReferences.length && !isLoading && !isDirtyList) { + return ( + + ) + } + return ( - - - - - - - - - - - + <> + + + + + + + + handleSearchChange('')} + /> + {isShowPagination && ( + + )} + ) } diff --git a/packages/ui/src/views/secrets/secret-entity-form.tsx b/packages/ui/src/views/secrets/secret-entity-form.tsx index 76e8dea77f..c4dedb3ccb 100644 --- a/packages/ui/src/views/secrets/secret-entity-form.tsx +++ b/packages/ui/src/views/secrets/secret-entity-form.tsx @@ -21,6 +21,7 @@ export type SecretEntityFormHandle = { interface SecretWrapper extends SecretListItem { secretManagerRef?: any + allowedSecretType?: 'SSHKey' | 'SecretFile' | 'SecretText' | 'WinRmCredentials' } interface SecretEntityFormProps { @@ -80,6 +81,7 @@ export const SecretEntityForm = forwardRef void isDrawer?: boolean + renderEntity?: (props: EntityRendererProps) => React.ReactNode // Pagination + showFilter?: boolean paginationProps?: { - totalItems?: number - currentPage?: number - goToPage?: (page: number) => void - pageSize?: number + handleLoadMore: () => void + isLastPage?: boolean + isLoading?: boolean } - showFilter?: boolean } // Component for selecting existing secrets @@ -60,31 +59,14 @@ export const SecretReference: React.FC = ({ handleChangeSearchValue, isDrawer = false, + renderEntity, // Pagination paginationProps, showFilter = true }) => { - // Custom entity renderer for secrets - const renderEntity = (props: EntityRendererProps) => { - const { entity, isSelected, onSelect } = props - - return ( - onSelect(entity)} - // thumbnail={} - > - - - ) - } - return (
- Secrets list - entities={secretsData} selectedEntity={selectedEntity} @@ -100,8 +82,8 @@ export const SecretReference: React.FC = ({ onFilterChange={onFilterChange} searchValue={searchValue} handleChangeSearchValue={handleChangeSearchValue} - paginationProps={paginationProps} showFilter={showFilter} + paginationProps={paginationProps} /> {!isDrawer && ( diff --git a/packages/ui/src/views/secrets/secrets-header.tsx b/packages/ui/src/views/secrets/secrets-header.tsx index 0b8e27e5e9..36c691311c 100644 --- a/packages/ui/src/views/secrets/secrets-header.tsx +++ b/packages/ui/src/views/secrets/secrets-header.tsx @@ -30,17 +30,17 @@ export const SecretsHeader = ({ } const options: Array> = [ - { - id: 'existing-secret', - title: 'Existing', - description: 'Use an existing secret.', - value: SecretType.EXISTING - }, { id: 'new-secret', title: 'New', description: 'Create a new secret.', value: SecretType.NEW + }, + { + id: 'existing-secret', + title: 'Existing', + description: 'Use an existing secret.', + value: SecretType.EXISTING } ] diff --git a/packages/ui/src/views/secrets/secrets-list/filter-options.tsx b/packages/ui/src/views/secrets/secrets-list/filter-options.tsx index c5deeedf71..56a1844691 100644 --- a/packages/ui/src/views/secrets/secrets-list/filter-options.tsx +++ b/packages/ui/src/views/secrets/secrets-list/filter-options.tsx @@ -26,7 +26,7 @@ export const getSecretListFilterOptions = ( ): FilterOptionConfig>[] => { return [ { - label: t('views:secrets.filterOptions.secretType.label', 'Secret Type'), + label: t('views:secrets.filterOptions.secretType.label', 'Type'), value: 'secretTypes', type: FilterFieldTypes.MultiSelect, filterFieldConfig: { @@ -35,7 +35,7 @@ export const getSecretListFilterOptions = ( parser: getMultiSelectParser(SECRET_TYPE_OPTIONS) }, { - label: t('views:secrets.filterOptions.secretManagerOption.label', 'Secret Manager Identifier'), + label: t('views:secrets.filterOptions.secretManagerOption.label', 'Secret Manager'), value: 'secretManagerIdentifiers', type: FilterFieldTypes.MultiSelect, filterFieldConfig: secretManagerFilterConfig, @@ -45,6 +45,11 @@ export const getSecretListFilterOptions = ( label: t('views:secrets.filterOptions.descriptionOption.label', 'Description'), value: 'description', type: FilterFieldTypes.Text + }, + { + label: t('views:secrets.filterOptions.tagsOption.label', 'Tags'), + value: 'tags', + type: FilterFieldTypes.Text } ] } diff --git a/packages/ui/src/views/secrets/secrets-list/secrets-list-page.tsx b/packages/ui/src/views/secrets/secrets-list/secrets-list-page.tsx index bc87efdfa2..23d593fb20 100644 --- a/packages/ui/src/views/secrets/secrets-list/secrets-list-page.tsx +++ b/packages/ui/src/views/secrets/secrets-list/secrets-list-page.tsx @@ -1,8 +1,8 @@ import { FC, useCallback, useMemo, useRef } from 'react' -import { Button, IconV2, Layout, NoData, Pagination, SecretListFilters, Text } from '@/components' -import { useRouterContext, useTranslation } from '@/context' -import { SandboxLayout } from '@/views' +import { IconV2, Layout, NoData, Pagination, PermissionIdentifier, ResourceType, SecretListFilters } from '@/components' +import { useComponents, useCustomDialogTrigger, useRouterContext, useTranslation } from '@/context' +import { Page, SandboxLayout } from '@/views' import { cn } from '@utils/cn' import FilterGroup, { FilterGroupRef } from '@views/components/FilterGroup' @@ -28,11 +28,19 @@ const SecretListPage: FC = ({ onDeleteSecret, onFilterChange, onSortChange, + scope, + routes, ...props }) => { const { t } = useTranslation() const { navigate } = useRouterContext() const filterRef = useRef(null) + const { RbacButton } = useComponents() + + const { accountId, orgIdentifier, projectIdentifier } = scope + + const { triggerRef: noDataTriggerRef, registerTrigger: registerNoDataTrigger } = useCustomDialogTrigger() + const { triggerRef, registerTrigger } = useCustomDialogTrigger() const secretManagerFilterOptions = secretManagerIdentifiers.map(secretManager => { return { @@ -93,12 +101,8 @@ const SecretListPage: FC = ({ ) ]} primaryButton={{ - label: ( - <> - - {t('views:notFound.button', 'Reload Page')} - - ), + icon: 'refresh', + label: t('views:notFound.button', 'Reload Page'), onClick: () => { navigate(0) // Reload the page } @@ -111,9 +115,24 @@ const SecretListPage: FC = ({ - - {t('views:secrets.secretsTitle', 'Secrets')} - + {isEmpty && ( = ({ title={t('views:noData.noSecrets', 'No secrets yet')} description={[t('views:noData.noSecrets', 'There are no secrets in this project yet.')]} primaryButton={{ - label: ( - <> - - {t('views:secrets.createNew', 'Create Secret')} - - ), - onClick: onCreate + ref: noDataTriggerRef, + icon: 'plus', + label: t('views:secrets.createNew', 'Create Secret'), + onClick: () => { + onCreate?.() + registerNoDataTrigger() + } }} /> )} @@ -144,10 +163,20 @@ const SecretListPage: FC = ({ onFilterValueChange={onFilterValueChange} handleInputChange={handleSearch} headerAction={ - + } filterOptions={SECRET_FILTER_OPTIONS} /> diff --git a/packages/ui/src/views/secrets/secrets-list/secrets-list.tsx b/packages/ui/src/views/secrets/secrets-list/secrets-list.tsx index cda273262e..b6c23fb8d3 100644 --- a/packages/ui/src/views/secrets/secrets-list/secrets-list.tsx +++ b/packages/ui/src/views/secrets/secrets-list/secrets-list.tsx @@ -1,7 +1,7 @@ import { useCallback } from 'react' -import { MoreActionsTooltip, NoData, Skeleton, Table, Text, TimeAgoCard, useCustomDialogTrigger } from '@/components' -import { useTranslation } from '@/context' +import { NoData, PermissionIdentifier, ResourceType, Skeleton, Table, Text, TimeAgoCard } from '@/components' +import { useComponents, useCustomDialogTrigger, useTranslation } from '@/context' import { cn } from '@utils/cn' import { SecretListProps } from './types' @@ -21,6 +21,7 @@ export function SecretList({ const { t } = useTranslation() const { triggerRef, registerTrigger } = useCustomDialogTrigger() + const { RbacMoreActionsTooltip } = useComponents() const handleDeleteSecret = useCallback( (secretId: string) => { @@ -58,41 +59,72 @@ export function SecretList({ {t('views:secret.title', 'Name')} - {t('views:common.manager', 'Secrets Manager')} - {t('views:common.lastActivity', 'Last Activity')} + {t('views:common.manager', 'Secret Manager')} + + {t('views:common.created', 'Created')} + + + {t('views:common.updated', 'Updated')} + {secrets.map(secret => ( - - {secret.identifier} + + {secret.name} - {secret.spec?.secretManagerIdentifier} + + {secret.spec?.secretManagerIdentifier === 'harnessSecretManager' + ? 'Harness' + : secret.spec?.secretManagerIdentifier} + - + + {secret?.createdAt ? ( + + ) : null} + + {secret?.updatedAt ? ( ) : null} - onEditSecret(secret) + onClick: () => { + registerTrigger() + onEditSecret(secret) + }, + rbac: { + resource: { + resourceType: ResourceType.SECRET, + resourceIdentifier: secret.identifier + }, + permissions: [PermissionIdentifier.UPDATE_SECRET] + } }, { isDanger: true, - title: t('views:secrets.delete', 'Delete Secret'), + title: t('views:secrets.delete', 'Delete secret'), iconName: 'trash', - onClick: () => handleDeleteSecret(secret.identifier) + onClick: () => handleDeleteSecret(secret.identifier), + rbac: { + resource: { + resourceType: ResourceType.SECRET, + resourceIdentifier: secret.identifier + }, + permissions: [PermissionIdentifier.DELETE_SECRET] + } } ]} /> diff --git a/packages/ui/src/views/secrets/secrets-list/types.ts b/packages/ui/src/views/secrets/secrets-list/types.ts index 67514dd275..4a17c4b4ff 100644 --- a/packages/ui/src/views/secrets/secrets-list/types.ts +++ b/packages/ui/src/views/secrets/secrets-list/types.ts @@ -1,3 +1,4 @@ +import { Scope } from '@/views/common/types' import { SecretListFilters } from '@components/filters' interface RoutingProps { @@ -50,4 +51,13 @@ export interface SecretListPageProps extends SecretListProps { goToPage: (page: number) => void onFilterChange?: (filters: SecretListFilters) => void onSortChange?: (sort: string) => void + scope: Scope + routes?: { + toSettings?: (params: { + accountId: string + orgIdentifier?: string + projectIdentifier?: string + module?: string + }) => string + } } diff --git a/packages/ui/src/views/sidebar-view/sidebar-view.tsx b/packages/ui/src/views/sidebar-view/sidebar-view.tsx index bc312ba5ad..10a786a4cd 100644 --- a/packages/ui/src/views/sidebar-view/sidebar-view.tsx +++ b/packages/ui/src/views/sidebar-view/sidebar-view.tsx @@ -132,7 +132,7 @@ export const SidebarView = ({ ))} - + - + = return (